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
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
//! Exceptions - begin/rescue/ensure type inference
|
|
2
|
+
//!
|
|
3
|
+
//! Collects types from each branch and merges them into a Union
|
|
4
|
+
//! via edges into a single result Vertex.
|
|
5
|
+
//! Applies the same MergeVertex pattern as conditionals.rs.
|
|
6
|
+
|
|
7
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
8
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
9
|
+
use crate::types::Type;
|
|
10
|
+
use ruby_prism::{BeginNode, RescueModifierNode, RescueNode};
|
|
11
|
+
|
|
12
|
+
use super::bytes_to_name;
|
|
13
|
+
use super::install::{install_node, install_statements};
|
|
14
|
+
|
|
15
|
+
/// Process BeginNode: begin/rescue/else/ensure
|
|
16
|
+
///
|
|
17
|
+
/// Type aggregation rules:
|
|
18
|
+
/// - No rescue clause: return begin body type directly
|
|
19
|
+
/// - With else clause: else type + all rescue types → Union (begin body excluded)
|
|
20
|
+
/// - Without else clause: begin body type + all rescue types → Union
|
|
21
|
+
/// - Ensure clause: processed for side effects only, does not affect return type
|
|
22
|
+
pub(crate) fn process_begin_node(
|
|
23
|
+
genv: &mut GlobalEnv,
|
|
24
|
+
lenv: &mut LocalEnv,
|
|
25
|
+
changes: &mut ChangeSet,
|
|
26
|
+
source: &str,
|
|
27
|
+
begin_node: &BeginNode,
|
|
28
|
+
) -> Option<VertexId> {
|
|
29
|
+
let begin_vtx = begin_node
|
|
30
|
+
.statements()
|
|
31
|
+
.and_then(|s| install_statements(genv, lenv, changes, source, &s));
|
|
32
|
+
|
|
33
|
+
let result = if let Some(rescue_node) = begin_node.rescue_clause() {
|
|
34
|
+
let result_vtx = genv.new_vertex();
|
|
35
|
+
|
|
36
|
+
process_rescue_chain(genv, lenv, changes, source, &rescue_node, result_vtx);
|
|
37
|
+
|
|
38
|
+
if let Some(else_node) = begin_node.else_clause() {
|
|
39
|
+
// With else: else type replaces begin body type (Ruby spec)
|
|
40
|
+
let else_vtx = else_node
|
|
41
|
+
.statements()
|
|
42
|
+
.and_then(|s| install_statements(genv, lenv, changes, source, &s));
|
|
43
|
+
if let Some(vtx) = else_vtx {
|
|
44
|
+
genv.add_edge(vtx, result_vtx);
|
|
45
|
+
}
|
|
46
|
+
} else if let Some(vtx) = begin_vtx {
|
|
47
|
+
genv.add_edge(vtx, result_vtx);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Some(result_vtx)
|
|
51
|
+
} else {
|
|
52
|
+
begin_vtx
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Ensure: side effects only, does not affect return type
|
|
56
|
+
if let Some(ensure_node) = begin_node.ensure_clause() {
|
|
57
|
+
if let Some(stmts) = ensure_node.statements() {
|
|
58
|
+
let _ = install_statements(genv, lenv, changes, source, &stmts);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
result
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Process RescueNode chain recursively.
|
|
66
|
+
/// Empty rescue body evaluates to nil.
|
|
67
|
+
fn process_rescue_chain(
|
|
68
|
+
genv: &mut GlobalEnv,
|
|
69
|
+
lenv: &mut LocalEnv,
|
|
70
|
+
changes: &mut ChangeSet,
|
|
71
|
+
source: &str,
|
|
72
|
+
rescue_node: &RescueNode,
|
|
73
|
+
result_vtx: VertexId,
|
|
74
|
+
) {
|
|
75
|
+
let body_vtx = process_rescue_body(genv, lenv, changes, source, rescue_node);
|
|
76
|
+
let vtx = body_vtx.unwrap_or_else(|| genv.new_source(Type::Nil));
|
|
77
|
+
genv.add_edge(vtx, result_vtx);
|
|
78
|
+
|
|
79
|
+
if let Some(next) = rescue_node.subsequent() {
|
|
80
|
+
process_rescue_chain(genv, lenv, changes, source, &next, result_vtx);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Extract the exception type from rescue node's exception class list.
|
|
85
|
+
/// Falls back to StandardError when no exceptions are specified or none can be resolved.
|
|
86
|
+
// TODO: Non-constant exception expressions (method calls, splats, variables) are silently skipped.
|
|
87
|
+
fn extract_exception_type(rescue_node: &RescueNode) -> Type {
|
|
88
|
+
let types: Vec<Type> = rescue_node
|
|
89
|
+
.exceptions()
|
|
90
|
+
.iter()
|
|
91
|
+
.filter_map(|exc| super::definitions::extract_constant_path(&exc))
|
|
92
|
+
.map(|name| Type::instance(&name))
|
|
93
|
+
.collect();
|
|
94
|
+
|
|
95
|
+
if types.is_empty() {
|
|
96
|
+
Type::instance("StandardError")
|
|
97
|
+
} else {
|
|
98
|
+
Type::union_of(types)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Process a single RescueNode body.
|
|
103
|
+
/// Registers the rescue variable (=> e), processes the body,
|
|
104
|
+
/// then removes the variable from scope.
|
|
105
|
+
fn process_rescue_body(
|
|
106
|
+
genv: &mut GlobalEnv,
|
|
107
|
+
lenv: &mut LocalEnv,
|
|
108
|
+
changes: &mut ChangeSet,
|
|
109
|
+
source: &str,
|
|
110
|
+
rescue_node: &RescueNode,
|
|
111
|
+
) -> Option<VertexId> {
|
|
112
|
+
for exc in &rescue_node.exceptions() {
|
|
113
|
+
install_node(genv, lenv, changes, source, &exc);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Save/restore rescue variable binding (=> e)
|
|
117
|
+
// TODO: Only LocalVariableTargetNode is handled; instance/global/class vars are not yet supported.
|
|
118
|
+
let var_binding = if let Some(ref_node) = rescue_node.reference() {
|
|
119
|
+
ref_node.as_local_variable_target_node().map(|target| {
|
|
120
|
+
let name = bytes_to_name(target.name().as_slice());
|
|
121
|
+
let saved = lenv.get_var(&name);
|
|
122
|
+
let exception_vtx = genv.new_vertex();
|
|
123
|
+
let exception_type = extract_exception_type(rescue_node);
|
|
124
|
+
let exception_src = genv.new_source(exception_type);
|
|
125
|
+
genv.add_edge(exception_src, exception_vtx);
|
|
126
|
+
lenv.new_var(name.clone(), exception_vtx);
|
|
127
|
+
(name, saved)
|
|
128
|
+
})
|
|
129
|
+
} else {
|
|
130
|
+
None
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
let body_vtx = rescue_node
|
|
134
|
+
.statements()
|
|
135
|
+
.and_then(|s| install_statements(genv, lenv, changes, source, &s));
|
|
136
|
+
|
|
137
|
+
if let Some((name, saved)) = var_binding {
|
|
138
|
+
match saved {
|
|
139
|
+
Some(prev_vtx) => lenv.new_var(name, prev_vtx),
|
|
140
|
+
None => lenv.remove_var(&name),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
body_vtx
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Process RescueModifierNode: `expression rescue rescue_expression`
|
|
148
|
+
pub(crate) fn process_rescue_modifier_node(
|
|
149
|
+
genv: &mut GlobalEnv,
|
|
150
|
+
lenv: &mut LocalEnv,
|
|
151
|
+
changes: &mut ChangeSet,
|
|
152
|
+
source: &str,
|
|
153
|
+
node: &RescueModifierNode,
|
|
154
|
+
) -> Option<VertexId> {
|
|
155
|
+
let result_vtx = genv.new_vertex();
|
|
156
|
+
|
|
157
|
+
let expr_vtx = install_node(genv, lenv, changes, source, &node.expression());
|
|
158
|
+
if let Some(vtx) = expr_vtx {
|
|
159
|
+
genv.add_edge(vtx, result_vtx);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let rescue_vtx = install_node(genv, lenv, changes, source, &node.rescue_expression());
|
|
163
|
+
if let Some(vtx) = rescue_vtx {
|
|
164
|
+
genv.add_edge(vtx, result_vtx);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Some(result_vtx)
|
|
168
|
+
}
|
|
@@ -15,10 +15,11 @@ use super::definitions::{process_class_node, process_def_node, process_module_no
|
|
|
15
15
|
use super::exceptions::{process_begin_node, process_rescue_modifier_node};
|
|
16
16
|
use super::dispatch::{dispatch_needs_child, dispatch_simple, process_needs_child, DispatchResult};
|
|
17
17
|
use super::literals::install_literal_node;
|
|
18
|
-
use super::loops::{process_until_node, process_while_node};
|
|
18
|
+
use super::loops::{process_for_node, process_until_node, process_while_node};
|
|
19
19
|
use super::operators::{process_and_node, process_or_node};
|
|
20
20
|
use super::parentheses::process_parentheses_node;
|
|
21
21
|
use super::returns::process_return_node;
|
|
22
|
+
use super::super_calls;
|
|
22
23
|
|
|
23
24
|
/// Build graph from AST (public API wrapper)
|
|
24
25
|
pub struct AstInstaller<'a> {
|
|
@@ -89,12 +90,26 @@ pub(crate) fn install_node(
|
|
|
89
90
|
return process_rescue_modifier_node(genv, lenv, changes, source, &rescue_modifier);
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
// SuperNode: super(args) — explicit arguments
|
|
94
|
+
if let Some(super_node) = node.as_super_node() {
|
|
95
|
+
return super_calls::process_super_node(genv, lenv, changes, source, &super_node);
|
|
96
|
+
}
|
|
97
|
+
// ForwardingSuperNode: super — implicit argument forwarding
|
|
98
|
+
if let Some(fwd_super_node) = node.as_forwarding_super_node() {
|
|
99
|
+
return super_calls::process_forwarding_super_node(
|
|
100
|
+
genv, lenv, changes, source, &fwd_super_node,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
if let Some(while_node) = node.as_while_node() {
|
|
93
105
|
return process_while_node(genv, lenv, changes, source, &while_node);
|
|
94
106
|
}
|
|
95
107
|
if let Some(until_node) = node.as_until_node() {
|
|
96
108
|
return process_until_node(genv, lenv, changes, source, &until_node);
|
|
97
109
|
}
|
|
110
|
+
if let Some(for_node) = node.as_for_node() {
|
|
111
|
+
return process_for_node(genv, lenv, changes, source, &for_node);
|
|
112
|
+
}
|
|
98
113
|
|
|
99
114
|
if let Some(paren_node) = node.as_parentheses_node() {
|
|
100
115
|
return process_parentheses_node(genv, lenv, changes, source, &paren_node);
|
|
@@ -118,13 +118,9 @@ fn install_array_literal_elements(
|
|
|
118
118
|
|
|
119
119
|
let array_type = if element_types.is_empty() {
|
|
120
120
|
Type::array()
|
|
121
|
-
} else if element_types.len() == 1 {
|
|
122
|
-
let elem_type = element_types.into_iter().next().unwrap();
|
|
123
|
-
Type::array_of(elem_type)
|
|
124
121
|
} else {
|
|
125
122
|
let types_vec: Vec<Type> = element_types.into_iter().collect();
|
|
126
|
-
|
|
127
|
-
Type::array_of(union_type)
|
|
123
|
+
Type::array_of(Type::union_of(types_vec))
|
|
128
124
|
};
|
|
129
125
|
|
|
130
126
|
Some(genv.new_source(array_type))
|
|
@@ -176,18 +172,8 @@ fn install_hash_literal_elements(
|
|
|
176
172
|
let hash_type = if key_types.is_empty() || value_types.is_empty() {
|
|
177
173
|
Type::hash()
|
|
178
174
|
} else {
|
|
179
|
-
let key_type =
|
|
180
|
-
|
|
181
|
-
} else {
|
|
182
|
-
let types_vec: Vec<Type> = key_types.into_iter().collect();
|
|
183
|
-
Type::Union(types_vec)
|
|
184
|
-
};
|
|
185
|
-
let value_type = if value_types.len() == 1 {
|
|
186
|
-
value_types.into_iter().next().unwrap()
|
|
187
|
-
} else {
|
|
188
|
-
let types_vec: Vec<Type> = value_types.into_iter().collect();
|
|
189
|
-
Type::Union(types_vec)
|
|
190
|
-
};
|
|
175
|
+
let key_type = Type::union_of(key_types.into_iter().collect());
|
|
176
|
+
let value_type = Type::union_of(value_types.into_iter().collect());
|
|
191
177
|
Type::hash_of(key_type, value_type)
|
|
192
178
|
};
|
|
193
179
|
|
|
@@ -238,57 +224,3 @@ fn infer_range_element_type(
|
|
|
238
224
|
}
|
|
239
225
|
None
|
|
240
226
|
}
|
|
241
|
-
|
|
242
|
-
#[cfg(test)]
|
|
243
|
-
mod tests {
|
|
244
|
-
use super::*;
|
|
245
|
-
|
|
246
|
-
#[test]
|
|
247
|
-
fn test_install_string_literal() {
|
|
248
|
-
let mut genv = GlobalEnv::new();
|
|
249
|
-
let vtx = genv.new_source(Type::string());
|
|
250
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "String");
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
#[test]
|
|
254
|
-
fn test_install_integer_literal() {
|
|
255
|
-
let mut genv = GlobalEnv::new();
|
|
256
|
-
let vtx = genv.new_source(Type::integer());
|
|
257
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Integer");
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
#[test]
|
|
261
|
-
fn test_install_float_literal() {
|
|
262
|
-
let mut genv = GlobalEnv::new();
|
|
263
|
-
let vtx = genv.new_source(Type::float());
|
|
264
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Float");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
#[test]
|
|
268
|
-
fn test_install_regexp_literal() {
|
|
269
|
-
let mut genv = GlobalEnv::new();
|
|
270
|
-
let vtx = genv.new_source(Type::regexp());
|
|
271
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Regexp");
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
#[test]
|
|
275
|
-
fn test_install_interpolated_string_literal() {
|
|
276
|
-
let mut genv = GlobalEnv::new();
|
|
277
|
-
let vtx = genv.new_source(Type::string());
|
|
278
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "String");
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
#[test]
|
|
282
|
-
fn test_install_interpolated_symbol_literal() {
|
|
283
|
-
let mut genv = GlobalEnv::new();
|
|
284
|
-
let vtx = genv.new_source(Type::symbol());
|
|
285
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Symbol");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
#[test]
|
|
289
|
-
fn test_install_interpolated_regexp_literal() {
|
|
290
|
-
let mut genv = GlobalEnv::new();
|
|
291
|
-
let vtx = genv.new_source(Type::regexp());
|
|
292
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Regexp");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//! Loops - while/until loop type inference
|
|
2
|
+
//!
|
|
3
|
+
//! Ruby loop expressions evaluate to nil (except when break passes a value).
|
|
4
|
+
//! Break value propagation is not yet supported.
|
|
5
|
+
|
|
6
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
7
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
8
|
+
use crate::types::Type;
|
|
9
|
+
use ruby_prism::{ForNode, UntilNode, WhileNode};
|
|
10
|
+
|
|
11
|
+
use super::bytes_to_name;
|
|
12
|
+
use super::install::{install_node, install_statements};
|
|
13
|
+
|
|
14
|
+
/// Process WhileNode: `while predicate; statements; end`
|
|
15
|
+
///
|
|
16
|
+
/// Returns nil. Traverses predicate and body to register method calls
|
|
17
|
+
/// and variable assignments in the type graph.
|
|
18
|
+
pub(crate) fn process_while_node(
|
|
19
|
+
genv: &mut GlobalEnv,
|
|
20
|
+
lenv: &mut LocalEnv,
|
|
21
|
+
changes: &mut ChangeSet,
|
|
22
|
+
source: &str,
|
|
23
|
+
while_node: &WhileNode,
|
|
24
|
+
) -> Option<VertexId> {
|
|
25
|
+
install_node(genv, lenv, changes, source, &while_node.predicate());
|
|
26
|
+
|
|
27
|
+
if let Some(stmts) = while_node.statements() {
|
|
28
|
+
install_statements(genv, lenv, changes, source, &stmts);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Some(genv.new_source(Type::Nil))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Process UntilNode: `until predicate; statements; end`
|
|
35
|
+
///
|
|
36
|
+
/// Returns nil. Traverses predicate and body to register method calls
|
|
37
|
+
/// and variable assignments in the type graph.
|
|
38
|
+
pub(crate) fn process_until_node(
|
|
39
|
+
genv: &mut GlobalEnv,
|
|
40
|
+
lenv: &mut LocalEnv,
|
|
41
|
+
changes: &mut ChangeSet,
|
|
42
|
+
source: &str,
|
|
43
|
+
until_node: &UntilNode,
|
|
44
|
+
) -> Option<VertexId> {
|
|
45
|
+
install_node(genv, lenv, changes, source, &until_node.predicate());
|
|
46
|
+
|
|
47
|
+
if let Some(stmts) = until_node.statements() {
|
|
48
|
+
install_statements(genv, lenv, changes, source, &stmts);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Some(genv.new_source(Type::Nil))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Process ForNode: `for index in collection; statements; end`
|
|
55
|
+
///
|
|
56
|
+
/// Ruby's `for` does NOT create a new scope — the loop variable persists
|
|
57
|
+
/// after the loop. This differs from `collection.each { |x| }` which
|
|
58
|
+
/// creates a block scope.
|
|
59
|
+
///
|
|
60
|
+
/// Returns nil (consistent with while/until; Ruby's for returns the
|
|
61
|
+
/// collection, but the return value is rarely used in practice).
|
|
62
|
+
pub(crate) fn process_for_node(
|
|
63
|
+
genv: &mut GlobalEnv,
|
|
64
|
+
lenv: &mut LocalEnv,
|
|
65
|
+
changes: &mut ChangeSet,
|
|
66
|
+
source: &str,
|
|
67
|
+
for_node: &ForNode,
|
|
68
|
+
) -> Option<VertexId> {
|
|
69
|
+
let collection_vtx = install_node(genv, lenv, changes, source, &for_node.collection());
|
|
70
|
+
|
|
71
|
+
// TODO: MultiTargetNode (e.g., `for a, b in [[1, "x"]]`) is not yet supported
|
|
72
|
+
if let Some(target) = for_node.index().as_local_variable_target_node() {
|
|
73
|
+
let name = bytes_to_name(target.name().as_slice());
|
|
74
|
+
let var_vtx = genv.new_vertex();
|
|
75
|
+
|
|
76
|
+
// Array[T] or Range[T] → loop var gets T
|
|
77
|
+
let elem_type = collection_vtx
|
|
78
|
+
.and_then(|vtx| genv.get_source(vtx))
|
|
79
|
+
.and_then(|src| src.ty.type_args())
|
|
80
|
+
.and_then(|args| args.first().cloned());
|
|
81
|
+
if let Some(ty) = elem_type {
|
|
82
|
+
let elem_src = genv.new_source(ty);
|
|
83
|
+
genv.add_edge(elem_src, var_vtx);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
lenv.new_var(name, var_vtx);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if let Some(stmts) = for_node.statements() {
|
|
90
|
+
install_statements(genv, lenv, changes, source, &stmts);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Some(genv.new_source(Type::Nil))
|
|
94
|
+
}
|
|
@@ -13,6 +13,7 @@ mod operators;
|
|
|
13
13
|
mod parameters;
|
|
14
14
|
mod parentheses;
|
|
15
15
|
mod returns;
|
|
16
|
+
mod super_calls;
|
|
16
17
|
mod variables;
|
|
17
18
|
|
|
18
19
|
pub use install::AstInstaller;
|
|
@@ -30,18 +31,3 @@ pub use install::AstInstaller;
|
|
|
30
31
|
pub(crate) fn bytes_to_name(bytes: &[u8]) -> String {
|
|
31
32
|
String::from_utf8_lossy(bytes).to_string()
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
#[cfg(test)]
|
|
35
|
-
mod tests {
|
|
36
|
-
use super::bytes_to_name;
|
|
37
|
-
|
|
38
|
-
#[test]
|
|
39
|
-
fn test_bytes_to_name_valid_utf8() {
|
|
40
|
-
assert_eq!(bytes_to_name(b"hello"), "hello");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
#[test]
|
|
44
|
-
fn test_bytes_to_name_invalid_utf8_replaced() {
|
|
45
|
-
assert_eq!(bytes_to_name(b"hello\xff"), "hello\u{FFFD}");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//! Operators - logical operator type inference (&&, ||, !)
|
|
2
|
+
|
|
3
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
4
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
5
|
+
use crate::types::Type;
|
|
6
|
+
use ruby_prism::{AndNode, Node, OrNode};
|
|
7
|
+
|
|
8
|
+
use super::install::install_node;
|
|
9
|
+
|
|
10
|
+
/// Merge two branch nodes into a union type vertex.
|
|
11
|
+
fn process_binary_logical_op<'a>(
|
|
12
|
+
genv: &mut GlobalEnv,
|
|
13
|
+
lenv: &mut LocalEnv,
|
|
14
|
+
changes: &mut ChangeSet,
|
|
15
|
+
source: &str,
|
|
16
|
+
left: Node<'a>,
|
|
17
|
+
right: Node<'a>,
|
|
18
|
+
) -> Option<VertexId> {
|
|
19
|
+
let result_vtx = genv.new_vertex();
|
|
20
|
+
|
|
21
|
+
if let Some(vtx) = install_node(genv, lenv, changes, source, &left) {
|
|
22
|
+
genv.add_edge(vtx, result_vtx);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if let Some(vtx) = install_node(genv, lenv, changes, source, &right) {
|
|
26
|
+
genv.add_edge(vtx, result_vtx);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Some(result_vtx)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Process AndNode (a && b): Union(type(a), type(b))
|
|
33
|
+
///
|
|
34
|
+
/// Runtime: if `a` is falsy, returns `a`; otherwise returns `b`.
|
|
35
|
+
/// Static: conservatively produce Union(type(a), type(b)).
|
|
36
|
+
pub(crate) fn process_and_node(
|
|
37
|
+
genv: &mut GlobalEnv,
|
|
38
|
+
lenv: &mut LocalEnv,
|
|
39
|
+
changes: &mut ChangeSet,
|
|
40
|
+
source: &str,
|
|
41
|
+
and_node: &AndNode,
|
|
42
|
+
) -> Option<VertexId> {
|
|
43
|
+
process_binary_logical_op(genv, lenv, changes, source, and_node.left(), and_node.right())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Process OrNode (a || b): Union(type(a), type(b))
|
|
47
|
+
///
|
|
48
|
+
/// Runtime: if `a` is truthy, returns `a`; otherwise returns `b`.
|
|
49
|
+
/// Static: conservatively produce Union(type(a), type(b)).
|
|
50
|
+
pub(crate) fn process_or_node(
|
|
51
|
+
genv: &mut GlobalEnv,
|
|
52
|
+
lenv: &mut LocalEnv,
|
|
53
|
+
changes: &mut ChangeSet,
|
|
54
|
+
source: &str,
|
|
55
|
+
or_node: &OrNode,
|
|
56
|
+
) -> Option<VertexId> {
|
|
57
|
+
process_binary_logical_op(genv, lenv, changes, source, or_node.left(), or_node.right())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Process not operator (!expr): TrueClass | FalseClass
|
|
61
|
+
///
|
|
62
|
+
/// In ruby-prism, `!expr` is represented as a CallNode with method name "!".
|
|
63
|
+
/// Static approximation: we cannot determine the receiver's truthiness at
|
|
64
|
+
/// compile time, so conservatively return TrueClass | FalseClass for any `!` call.
|
|
65
|
+
/// In practice, `!nil` and `!false` are always true, but we do not track that here.
|
|
66
|
+
///
|
|
67
|
+
/// Receiver side effects are already analyzed by the caller (process_needs_child).
|
|
68
|
+
///
|
|
69
|
+
/// TODO: Ruby allows overriding `BasicObject#!`. Currently we always return
|
|
70
|
+
/// TrueClass | FalseClass, ignoring user-defined `!` methods. If needed, look up
|
|
71
|
+
/// the receiver's RBS definition and use its return type instead.
|
|
72
|
+
pub(crate) fn process_not_operator(genv: &mut GlobalEnv) -> VertexId {
|
|
73
|
+
let result_vtx = genv.new_vertex();
|
|
74
|
+
let true_vtx = genv.new_source(Type::instance("TrueClass"));
|
|
75
|
+
let false_vtx = genv.new_source(Type::instance("FalseClass"));
|
|
76
|
+
genv.add_edge(true_vtx, result_vtx);
|
|
77
|
+
genv.add_edge(false_vtx, result_vtx);
|
|
78
|
+
result_vtx
|
|
79
|
+
}
|
|
@@ -24,7 +24,7 @@ use super::bytes_to_name;
|
|
|
24
24
|
/// name.upcase
|
|
25
25
|
/// end
|
|
26
26
|
/// ```
|
|
27
|
-
pub fn install_required_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
|
|
27
|
+
pub(crate) fn install_required_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
|
|
28
28
|
// Create a vertex for the parameter (starts as Bot/untyped)
|
|
29
29
|
let param_vtx = genv.new_vertex();
|
|
30
30
|
|
|
@@ -44,7 +44,7 @@ pub fn install_required_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, nam
|
|
|
44
44
|
/// name.upcase
|
|
45
45
|
/// end
|
|
46
46
|
/// ```
|
|
47
|
-
pub fn install_optional_parameter(
|
|
47
|
+
pub(crate) fn install_optional_parameter(
|
|
48
48
|
genv: &mut GlobalEnv,
|
|
49
49
|
lenv: &mut LocalEnv,
|
|
50
50
|
_changes: &mut ChangeSet,
|
|
@@ -75,7 +75,7 @@ pub fn install_optional_parameter(
|
|
|
75
75
|
/// items.first
|
|
76
76
|
/// end
|
|
77
77
|
/// ```
|
|
78
|
-
pub fn install_rest_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
|
|
78
|
+
pub(crate) fn install_rest_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
|
|
79
79
|
// Create a vertex for the parameter
|
|
80
80
|
let param_vtx = genv.new_vertex();
|
|
81
81
|
|
|
@@ -99,7 +99,7 @@ pub fn install_rest_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: S
|
|
|
99
99
|
/// options[:debug]
|
|
100
100
|
/// end
|
|
101
101
|
/// ```
|
|
102
|
-
pub fn install_keyword_rest_parameter(
|
|
102
|
+
pub(crate) fn install_keyword_rest_parameter(
|
|
103
103
|
genv: &mut GlobalEnv,
|
|
104
104
|
lenv: &mut LocalEnv,
|
|
105
105
|
name: String,
|
|
@@ -204,66 +204,3 @@ pub(crate) fn install_parameters(
|
|
|
204
204
|
|
|
205
205
|
(param_vtxs, keyword_param_vtxs)
|
|
206
206
|
}
|
|
207
|
-
|
|
208
|
-
#[cfg(test)]
|
|
209
|
-
mod tests {
|
|
210
|
-
use super::*;
|
|
211
|
-
|
|
212
|
-
#[test]
|
|
213
|
-
fn test_install_required_parameter() {
|
|
214
|
-
let mut genv = GlobalEnv::new();
|
|
215
|
-
let mut lenv = LocalEnv::new();
|
|
216
|
-
|
|
217
|
-
let vtx = install_required_parameter(&mut genv, &mut lenv, "name".to_string());
|
|
218
|
-
|
|
219
|
-
// Parameter should be registered in LocalEnv
|
|
220
|
-
assert_eq!(lenv.get_var("name"), Some(vtx));
|
|
221
|
-
|
|
222
|
-
// Vertex should exist in GlobalEnv (as untyped)
|
|
223
|
-
let vertex = genv.get_vertex(vtx);
|
|
224
|
-
assert!(vertex.is_some());
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
#[test]
|
|
228
|
-
fn test_install_multiple_parameters() {
|
|
229
|
-
let mut genv = GlobalEnv::new();
|
|
230
|
-
let mut lenv = LocalEnv::new();
|
|
231
|
-
|
|
232
|
-
let vtx_a = install_required_parameter(&mut genv, &mut lenv, "a".to_string());
|
|
233
|
-
let vtx_b = install_required_parameter(&mut genv, &mut lenv, "b".to_string());
|
|
234
|
-
let vtx_c = install_required_parameter(&mut genv, &mut lenv, "c".to_string());
|
|
235
|
-
|
|
236
|
-
// All parameters should be registered
|
|
237
|
-
assert_eq!(lenv.get_var("a"), Some(vtx_a));
|
|
238
|
-
assert_eq!(lenv.get_var("b"), Some(vtx_b));
|
|
239
|
-
assert_eq!(lenv.get_var("c"), Some(vtx_c));
|
|
240
|
-
|
|
241
|
-
// All vertices should be different
|
|
242
|
-
assert_ne!(vtx_a, vtx_b);
|
|
243
|
-
assert_ne!(vtx_b, vtx_c);
|
|
244
|
-
assert_ne!(vtx_a, vtx_c);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
#[test]
|
|
248
|
-
fn test_install_optional_parameter_inherits_default_type() {
|
|
249
|
-
let mut genv = GlobalEnv::new();
|
|
250
|
-
let mut lenv = LocalEnv::new();
|
|
251
|
-
let mut changes = ChangeSet::new();
|
|
252
|
-
|
|
253
|
-
// Default value: 0 (Integer)
|
|
254
|
-
let default_vtx = genv.new_source(Type::integer());
|
|
255
|
-
let vtx = install_optional_parameter(
|
|
256
|
-
&mut genv,
|
|
257
|
-
&mut lenv,
|
|
258
|
-
&mut changes,
|
|
259
|
-
"age".to_string(),
|
|
260
|
-
default_vtx,
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
assert_eq!(lenv.get_var("age"), Some(vtx));
|
|
264
|
-
|
|
265
|
-
// Type should propagate from default value
|
|
266
|
-
let vertex = genv.get_vertex(vtx).unwrap();
|
|
267
|
-
assert_eq!(vertex.show(), "Integer");
|
|
268
|
-
}
|
|
269
|
-
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//! Parentheses - pass-through type propagation for parenthesized expressions
|
|
2
|
+
|
|
3
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
4
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
5
|
+
|
|
6
|
+
use super::install::{install_node, install_statements};
|
|
7
|
+
|
|
8
|
+
/// Process ParenthesesNode: propagate inner expression's type
|
|
9
|
+
pub(crate) fn process_parentheses_node(
|
|
10
|
+
genv: &mut GlobalEnv,
|
|
11
|
+
lenv: &mut LocalEnv,
|
|
12
|
+
changes: &mut ChangeSet,
|
|
13
|
+
source: &str,
|
|
14
|
+
paren_node: &ruby_prism::ParenthesesNode,
|
|
15
|
+
) -> Option<VertexId> {
|
|
16
|
+
let body = paren_node.body()?;
|
|
17
|
+
|
|
18
|
+
if let Some(stmts) = body.as_statements_node() {
|
|
19
|
+
// (expr1; expr2) → process all, return last expression's type
|
|
20
|
+
install_statements(genv, lenv, changes, source, &stmts)
|
|
21
|
+
} else {
|
|
22
|
+
// (expr) → propagate inner expression's type directly
|
|
23
|
+
install_node(genv, lenv, changes, source, &body)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//! Return statement handling
|
|
2
|
+
//!
|
|
3
|
+
//! Processes `return expr` by connecting the expression's vertex
|
|
4
|
+
//! to the enclosing method's merge vertex.
|
|
5
|
+
|
|
6
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
7
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
8
|
+
|
|
9
|
+
use super::install::install_node;
|
|
10
|
+
|
|
11
|
+
/// Process ReturnNode: connect return value to method's merge vertex
|
|
12
|
+
pub(crate) fn process_return_node(
|
|
13
|
+
genv: &mut GlobalEnv,
|
|
14
|
+
lenv: &mut LocalEnv,
|
|
15
|
+
changes: &mut ChangeSet,
|
|
16
|
+
source: &str,
|
|
17
|
+
return_node: &ruby_prism::ReturnNode,
|
|
18
|
+
) -> Option<VertexId> {
|
|
19
|
+
// Process return value (first argument only; multi-value return not yet supported)
|
|
20
|
+
let value_vtx = if let Some(arguments) = return_node.arguments() {
|
|
21
|
+
arguments
|
|
22
|
+
.arguments()
|
|
23
|
+
.iter()
|
|
24
|
+
.next()
|
|
25
|
+
.and_then(|arg| install_node(genv, lenv, changes, source, &arg))
|
|
26
|
+
} else {
|
|
27
|
+
// `return` without value → nil
|
|
28
|
+
Some(genv.new_source(crate::types::Type::Nil))
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Connect return value to method's merge vertex
|
|
32
|
+
if let Some(vtx) = value_vtx {
|
|
33
|
+
if let Some(merge_vtx) = genv.scope_manager.current_method_return_vertex() {
|
|
34
|
+
genv.add_edge(vtx, merge_vtx);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
None
|
|
39
|
+
}
|