method-ray 0.1.3 → 0.1.5
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 +37 -0
- data/ext/Cargo.toml +1 -1
- data/ext/src/lib.rs +38 -59
- data/lib/methodray/cli.rb +1 -0
- data/lib/methodray/commands.rb +0 -1
- data/lib/methodray/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/analyzer/attributes.rs +57 -0
- data/rust/src/analyzer/blocks.rs +104 -23
- data/rust/src/analyzer/calls.rs +7 -4
- data/rust/src/analyzer/conditionals.rs +538 -0
- data/rust/src/analyzer/definitions.rs +503 -8
- data/rust/src/analyzer/dispatch.rs +765 -11
- data/rust/src/analyzer/install.rs +63 -863
- data/rust/src/analyzer/literals.rs +223 -29
- data/rust/src/analyzer/mod.rs +5 -0
- data/rust/src/analyzer/operators.rs +192 -0
- data/rust/src/analyzer/parameters.rs +64 -0
- data/rust/src/analyzer/parentheses.rs +113 -0
- data/rust/src/analyzer/returns.rs +191 -0
- data/rust/src/analyzer/variables.rs +12 -3
- data/rust/src/checker.rs +2 -4
- data/rust/src/cli/args.rs +1 -1
- data/rust/src/env/global_env.rs +15 -4
- data/rust/src/env/method_registry.rs +55 -0
- data/rust/src/env/scope.rs +21 -0
- data/rust/src/graph/box.rs +147 -5
- data/rust/src/main.rs +1 -0
- metadata +6 -1
|
@@ -3,60 +3,239 @@
|
|
|
3
3
|
//! This module is responsible for:
|
|
4
4
|
//! - String, Integer, Float, Regexp literals
|
|
5
5
|
//! - nil, true, false, Symbol literals
|
|
6
|
+
//! - Array, Hash, Range literals with element type inference
|
|
6
7
|
//! - Creating Source vertices with fixed types
|
|
7
|
-
//!
|
|
8
|
-
//! Note: Array, Hash, and Range literals are handled in install.rs for element type inference
|
|
9
8
|
|
|
10
|
-
use crate::env::GlobalEnv;
|
|
11
|
-
use crate::graph::VertexId;
|
|
9
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
10
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
12
11
|
use crate::types::Type;
|
|
13
12
|
use ruby_prism::Node;
|
|
13
|
+
use std::collections::HashSet;
|
|
14
14
|
|
|
15
|
-
/// Install literal
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
/// Install literal node (including complex types: Array, Hash, Range)
|
|
16
|
+
pub(crate) fn install_literal_node(
|
|
17
|
+
genv: &mut GlobalEnv,
|
|
18
|
+
lenv: &mut LocalEnv,
|
|
19
|
+
changes: &mut ChangeSet,
|
|
20
|
+
source: &str,
|
|
21
|
+
node: &Node,
|
|
22
|
+
) -> Option<VertexId> {
|
|
23
|
+
if node.as_array_node().is_some() {
|
|
24
|
+
let elements: Vec<Node> = node.as_array_node().unwrap().elements().iter().collect();
|
|
25
|
+
return install_array_literal_elements(genv, lenv, changes, source, elements);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if node.as_hash_node().is_some() {
|
|
29
|
+
let elements: Vec<Node> = node.as_hash_node().unwrap().elements().iter().collect();
|
|
30
|
+
return install_hash_literal_elements(genv, lenv, changes, source, elements);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if let Some(range_node) = node.as_range_node() {
|
|
34
|
+
return install_range_literal(genv, lenv, changes, source, &range_node);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// InterpolatedStringNode: "Hello, #{expr}" → String
|
|
38
|
+
if let Some(interp) = node.as_interpolated_string_node() {
|
|
39
|
+
for part in &interp.parts() {
|
|
40
|
+
super::install::install_node(genv, lenv, changes, source, &part);
|
|
41
|
+
}
|
|
22
42
|
return Some(genv.new_source(Type::string()));
|
|
23
43
|
}
|
|
24
44
|
|
|
25
|
-
//
|
|
45
|
+
// InterpolatedSymbolNode: :"hello_#{expr}" → Symbol
|
|
46
|
+
if let Some(interp) = node.as_interpolated_symbol_node() {
|
|
47
|
+
for part in &interp.parts() {
|
|
48
|
+
super::install::install_node(genv, lenv, changes, source, &part);
|
|
49
|
+
}
|
|
50
|
+
return Some(genv.new_source(Type::symbol()));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// InterpolatedRegularExpressionNode: /hello #{expr}/ → Regexp
|
|
54
|
+
if let Some(interp) = node.as_interpolated_regular_expression_node() {
|
|
55
|
+
for part in &interp.parts() {
|
|
56
|
+
super::install::install_node(genv, lenv, changes, source, &part);
|
|
57
|
+
}
|
|
58
|
+
return Some(genv.new_source(Type::regexp()));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
install_simple_literal(genv, node)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Install simple literal nodes (String, Integer, Float, nil, true, false, Symbol, Regexp)
|
|
65
|
+
fn install_simple_literal(genv: &mut GlobalEnv, node: &Node) -> Option<VertexId> {
|
|
66
|
+
if node.as_string_node().is_some() {
|
|
67
|
+
return Some(genv.new_source(Type::string()));
|
|
68
|
+
}
|
|
26
69
|
if node.as_integer_node().is_some() {
|
|
27
70
|
return Some(genv.new_source(Type::integer()));
|
|
28
71
|
}
|
|
29
|
-
|
|
30
|
-
// 3.14
|
|
31
72
|
if node.as_float_node().is_some() {
|
|
32
73
|
return Some(genv.new_source(Type::float()));
|
|
33
74
|
}
|
|
34
|
-
|
|
35
|
-
// nil
|
|
36
75
|
if node.as_nil_node().is_some() {
|
|
37
76
|
return Some(genv.new_source(Type::Nil));
|
|
38
77
|
}
|
|
39
|
-
|
|
40
|
-
// true
|
|
41
78
|
if node.as_true_node().is_some() {
|
|
42
79
|
return Some(genv.new_source(Type::instance("TrueClass")));
|
|
43
80
|
}
|
|
44
|
-
|
|
45
|
-
// false
|
|
46
81
|
if node.as_false_node().is_some() {
|
|
47
82
|
return Some(genv.new_source(Type::instance("FalseClass")));
|
|
48
83
|
}
|
|
49
|
-
|
|
50
|
-
// :symbol
|
|
51
84
|
if node.as_symbol_node().is_some() {
|
|
52
85
|
return Some(genv.new_source(Type::symbol()));
|
|
53
86
|
}
|
|
54
|
-
|
|
55
|
-
// /pattern/
|
|
56
87
|
if node.as_regular_expression_node().is_some() {
|
|
57
88
|
return Some(genv.new_source(Type::regexp()));
|
|
58
89
|
}
|
|
90
|
+
None
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Install array literal with element type inference
|
|
94
|
+
fn install_array_literal_elements(
|
|
95
|
+
genv: &mut GlobalEnv,
|
|
96
|
+
lenv: &mut LocalEnv,
|
|
97
|
+
changes: &mut ChangeSet,
|
|
98
|
+
source: &str,
|
|
99
|
+
elements: Vec<Node>,
|
|
100
|
+
) -> Option<VertexId> {
|
|
101
|
+
if elements.is_empty() {
|
|
102
|
+
return Some(genv.new_source(Type::array()));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let mut element_types: HashSet<Type> = HashSet::new();
|
|
59
106
|
|
|
107
|
+
for element in &elements {
|
|
108
|
+
if let Some(vtx) = super::install::install_node(genv, lenv, changes, source, element) {
|
|
109
|
+
if let Some(src) = genv.get_source(vtx) {
|
|
110
|
+
element_types.insert(src.ty.clone());
|
|
111
|
+
} else if let Some(vertex) = genv.get_vertex(vtx) {
|
|
112
|
+
for ty in vertex.types.keys() {
|
|
113
|
+
element_types.insert(ty.clone());
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let array_type = if element_types.is_empty() {
|
|
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
|
+
} else {
|
|
125
|
+
let types_vec: Vec<Type> = element_types.into_iter().collect();
|
|
126
|
+
let union_type = Type::Union(types_vec);
|
|
127
|
+
Type::array_of(union_type)
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
Some(genv.new_source(array_type))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Install hash literal with key/value type inference
|
|
134
|
+
fn install_hash_literal_elements(
|
|
135
|
+
genv: &mut GlobalEnv,
|
|
136
|
+
lenv: &mut LocalEnv,
|
|
137
|
+
changes: &mut ChangeSet,
|
|
138
|
+
source: &str,
|
|
139
|
+
elements: Vec<Node>,
|
|
140
|
+
) -> Option<VertexId> {
|
|
141
|
+
if elements.is_empty() {
|
|
142
|
+
return Some(genv.new_source(Type::hash()));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let mut key_types: HashSet<Type> = HashSet::new();
|
|
146
|
+
let mut value_types: HashSet<Type> = HashSet::new();
|
|
147
|
+
|
|
148
|
+
for element in &elements {
|
|
149
|
+
if let Some(assoc_node) = element.as_assoc_node() {
|
|
150
|
+
if let Some(key_vtx) =
|
|
151
|
+
super::install::install_node(genv, lenv, changes, source, &assoc_node.key())
|
|
152
|
+
{
|
|
153
|
+
if let Some(src) = genv.get_source(key_vtx) {
|
|
154
|
+
key_types.insert(src.ty.clone());
|
|
155
|
+
} else if let Some(vertex) = genv.get_vertex(key_vtx) {
|
|
156
|
+
for ty in vertex.types.keys() {
|
|
157
|
+
key_types.insert(ty.clone());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if let Some(value_vtx) =
|
|
163
|
+
super::install::install_node(genv, lenv, changes, source, &assoc_node.value())
|
|
164
|
+
{
|
|
165
|
+
if let Some(src) = genv.get_source(value_vtx) {
|
|
166
|
+
value_types.insert(src.ty.clone());
|
|
167
|
+
} else if let Some(vertex) = genv.get_vertex(value_vtx) {
|
|
168
|
+
for ty in vertex.types.keys() {
|
|
169
|
+
value_types.insert(ty.clone());
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let hash_type = if key_types.is_empty() || value_types.is_empty() {
|
|
177
|
+
Type::hash()
|
|
178
|
+
} else {
|
|
179
|
+
let key_type = if key_types.len() == 1 {
|
|
180
|
+
key_types.into_iter().next().unwrap()
|
|
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
|
+
};
|
|
191
|
+
Type::hash_of(key_type, value_type)
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
Some(genv.new_source(hash_type))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Install range literal with element type inference
|
|
198
|
+
fn install_range_literal(
|
|
199
|
+
genv: &mut GlobalEnv,
|
|
200
|
+
lenv: &mut LocalEnv,
|
|
201
|
+
changes: &mut ChangeSet,
|
|
202
|
+
source: &str,
|
|
203
|
+
range_node: &ruby_prism::RangeNode,
|
|
204
|
+
) -> Option<VertexId> {
|
|
205
|
+
let element_type = if let Some(left) = range_node.left() {
|
|
206
|
+
infer_range_element_type(genv, lenv, changes, source, &left)
|
|
207
|
+
} else if let Some(right) = range_node.right() {
|
|
208
|
+
infer_range_element_type(genv, lenv, changes, source, &right)
|
|
209
|
+
} else {
|
|
210
|
+
None
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
let range_type = match element_type {
|
|
214
|
+
Some(ty) => Type::range_of(ty),
|
|
215
|
+
None => Type::range(),
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
Some(genv.new_source(range_type))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Infer element type from a range endpoint node
|
|
222
|
+
fn infer_range_element_type(
|
|
223
|
+
genv: &mut GlobalEnv,
|
|
224
|
+
lenv: &mut LocalEnv,
|
|
225
|
+
changes: &mut ChangeSet,
|
|
226
|
+
source: &str,
|
|
227
|
+
node: &Node,
|
|
228
|
+
) -> Option<Type> {
|
|
229
|
+
if let Some(vtx) = super::install::install_node(genv, lenv, changes, source, node) {
|
|
230
|
+
if let Some(src) = genv.get_source(vtx) {
|
|
231
|
+
return Some(src.ty.clone());
|
|
232
|
+
}
|
|
233
|
+
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
234
|
+
if let Some(ty) = vertex.types.keys().next() {
|
|
235
|
+
return Some(ty.clone());
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
60
239
|
None
|
|
61
240
|
}
|
|
62
241
|
|
|
@@ -67,9 +246,6 @@ mod tests {
|
|
|
67
246
|
#[test]
|
|
68
247
|
fn test_install_string_literal() {
|
|
69
248
|
let mut genv = GlobalEnv::new();
|
|
70
|
-
|
|
71
|
-
// Create a mock string node - we test via integration instead
|
|
72
|
-
// Unit test just verifies the type creation
|
|
73
249
|
let vtx = genv.new_source(Type::string());
|
|
74
250
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "String");
|
|
75
251
|
}
|
|
@@ -77,7 +253,6 @@ mod tests {
|
|
|
77
253
|
#[test]
|
|
78
254
|
fn test_install_integer_literal() {
|
|
79
255
|
let mut genv = GlobalEnv::new();
|
|
80
|
-
|
|
81
256
|
let vtx = genv.new_source(Type::integer());
|
|
82
257
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Integer");
|
|
83
258
|
}
|
|
@@ -85,7 +260,6 @@ mod tests {
|
|
|
85
260
|
#[test]
|
|
86
261
|
fn test_install_float_literal() {
|
|
87
262
|
let mut genv = GlobalEnv::new();
|
|
88
|
-
|
|
89
263
|
let vtx = genv.new_source(Type::float());
|
|
90
264
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Float");
|
|
91
265
|
}
|
|
@@ -93,7 +267,27 @@ mod tests {
|
|
|
93
267
|
#[test]
|
|
94
268
|
fn test_install_regexp_literal() {
|
|
95
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
|
+
}
|
|
96
287
|
|
|
288
|
+
#[test]
|
|
289
|
+
fn test_install_interpolated_regexp_literal() {
|
|
290
|
+
let mut genv = GlobalEnv::new();
|
|
97
291
|
let vtx = genv.new_source(Type::regexp());
|
|
98
292
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Regexp");
|
|
99
293
|
}
|
data/rust/src/analyzer/mod.rs
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
mod attributes;
|
|
1
2
|
mod blocks;
|
|
2
3
|
mod calls;
|
|
4
|
+
mod conditionals;
|
|
3
5
|
mod definitions;
|
|
4
6
|
mod dispatch;
|
|
5
7
|
mod install;
|
|
6
8
|
mod literals;
|
|
9
|
+
mod operators;
|
|
7
10
|
mod parameters;
|
|
11
|
+
mod parentheses;
|
|
12
|
+
mod returns;
|
|
8
13
|
mod variables;
|
|
9
14
|
|
|
10
15
|
pub use install::AstInstaller;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
//! Operators - logical operator type inference (&&, ||)
|
|
2
|
+
|
|
3
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
4
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
5
|
+
use ruby_prism::{AndNode, OrNode};
|
|
6
|
+
|
|
7
|
+
use super::install::install_node;
|
|
8
|
+
|
|
9
|
+
/// Process AndNode (a && b): Union(type(a), type(b))
|
|
10
|
+
///
|
|
11
|
+
/// Short-circuit semantics: if `a` is falsy, returns `a`; otherwise returns `b`.
|
|
12
|
+
/// Static approximation: we cannot determine truthiness at compile time,
|
|
13
|
+
/// so we conservatively produce Union(type(a), type(b)).
|
|
14
|
+
pub(crate) fn process_and_node(
|
|
15
|
+
genv: &mut GlobalEnv,
|
|
16
|
+
lenv: &mut LocalEnv,
|
|
17
|
+
changes: &mut ChangeSet,
|
|
18
|
+
source: &str,
|
|
19
|
+
and_node: &AndNode,
|
|
20
|
+
) -> Option<VertexId> {
|
|
21
|
+
let result_vtx = genv.new_vertex();
|
|
22
|
+
|
|
23
|
+
let left_vtx = install_node(genv, lenv, changes, source, &and_node.left());
|
|
24
|
+
if let Some(vtx) = left_vtx {
|
|
25
|
+
genv.add_edge(vtx, result_vtx);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let right_vtx = install_node(genv, lenv, changes, source, &and_node.right());
|
|
29
|
+
if let Some(vtx) = right_vtx {
|
|
30
|
+
genv.add_edge(vtx, result_vtx);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Some(result_vtx)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Process OrNode (a || b): Union(type(a), type(b))
|
|
37
|
+
///
|
|
38
|
+
/// Short-circuit semantics: if `a` is truthy, returns `a`; otherwise returns `b`.
|
|
39
|
+
/// Static approximation: identical to AndNode — Union of both sides.
|
|
40
|
+
pub(crate) fn process_or_node(
|
|
41
|
+
genv: &mut GlobalEnv,
|
|
42
|
+
lenv: &mut LocalEnv,
|
|
43
|
+
changes: &mut ChangeSet,
|
|
44
|
+
source: &str,
|
|
45
|
+
or_node: &OrNode,
|
|
46
|
+
) -> Option<VertexId> {
|
|
47
|
+
let result_vtx = genv.new_vertex();
|
|
48
|
+
|
|
49
|
+
let left_vtx = install_node(genv, lenv, changes, source, &or_node.left());
|
|
50
|
+
if let Some(vtx) = left_vtx {
|
|
51
|
+
genv.add_edge(vtx, result_vtx);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let right_vtx = install_node(genv, lenv, changes, source, &or_node.right());
|
|
55
|
+
if let Some(vtx) = right_vtx {
|
|
56
|
+
genv.add_edge(vtx, result_vtx);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Some(result_vtx)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[cfg(test)]
|
|
63
|
+
mod tests {
|
|
64
|
+
use crate::analyzer::install::AstInstaller;
|
|
65
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
66
|
+
use crate::graph::VertexId;
|
|
67
|
+
use crate::parser::ParseSession;
|
|
68
|
+
use crate::types::Type;
|
|
69
|
+
|
|
70
|
+
/// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
|
|
71
|
+
fn analyze(source: &str) -> GlobalEnv {
|
|
72
|
+
let session = ParseSession::new();
|
|
73
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
74
|
+
let root = parse_result.node();
|
|
75
|
+
let program = root.as_program_node().unwrap();
|
|
76
|
+
|
|
77
|
+
let mut genv = GlobalEnv::new();
|
|
78
|
+
let mut lenv = LocalEnv::new();
|
|
79
|
+
|
|
80
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
81
|
+
for stmt in &program.statements().body() {
|
|
82
|
+
installer.install_node(&stmt);
|
|
83
|
+
}
|
|
84
|
+
installer.finish();
|
|
85
|
+
|
|
86
|
+
genv
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Helper: get the type string for a vertex ID
|
|
90
|
+
fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
|
|
91
|
+
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
92
|
+
vertex.show()
|
|
93
|
+
} else if let Some(source) = genv.get_source(vtx) {
|
|
94
|
+
source.ty.show()
|
|
95
|
+
} else {
|
|
96
|
+
panic!("vertex {:?} not found as either Vertex or Source", vtx);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[test]
|
|
101
|
+
fn test_and_node_union_type() {
|
|
102
|
+
let source = r#"
|
|
103
|
+
class Foo
|
|
104
|
+
def bar
|
|
105
|
+
true && "hello"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
"#;
|
|
109
|
+
let genv = analyze(source);
|
|
110
|
+
let info = genv
|
|
111
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
112
|
+
.expect("Foo#bar should be registered");
|
|
113
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
114
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
115
|
+
assert!(type_str.contains("TrueClass"), "should contain TrueClass: {}", type_str);
|
|
116
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[test]
|
|
120
|
+
fn test_and_node_same_type() {
|
|
121
|
+
let source = r#"
|
|
122
|
+
class Foo
|
|
123
|
+
def bar
|
|
124
|
+
"a" && "b"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
"#;
|
|
128
|
+
let genv = analyze(source);
|
|
129
|
+
let info = genv
|
|
130
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
131
|
+
.expect("Foo#bar should be registered");
|
|
132
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
133
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[test]
|
|
137
|
+
fn test_or_node_union_type() {
|
|
138
|
+
let source = r#"
|
|
139
|
+
class Foo
|
|
140
|
+
def bar
|
|
141
|
+
42 || "hello"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
"#;
|
|
145
|
+
let genv = analyze(source);
|
|
146
|
+
let info = genv
|
|
147
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
148
|
+
.expect("Foo#bar should be registered");
|
|
149
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
150
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
151
|
+
assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
|
|
152
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[test]
|
|
156
|
+
fn test_or_node_same_type() {
|
|
157
|
+
let source = r#"
|
|
158
|
+
class Foo
|
|
159
|
+
def bar
|
|
160
|
+
1 || 2
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
"#;
|
|
164
|
+
let genv = analyze(source);
|
|
165
|
+
let info = genv
|
|
166
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
167
|
+
.expect("Foo#bar should be registered");
|
|
168
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
169
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#[test]
|
|
173
|
+
fn test_nested_logical_operators() {
|
|
174
|
+
let source = r#"
|
|
175
|
+
class Foo
|
|
176
|
+
def bar
|
|
177
|
+
1 && "a" || :b
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
"#;
|
|
181
|
+
let genv = analyze(source);
|
|
182
|
+
let info = genv
|
|
183
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
184
|
+
.expect("Foo#bar should be registered");
|
|
185
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
186
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
187
|
+
assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
|
|
188
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
189
|
+
assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
}
|
|
@@ -113,6 +113,70 @@ pub fn install_keyword_rest_parameter(
|
|
|
113
113
|
param_vtx
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/// Install method parameters as local variables
|
|
117
|
+
///
|
|
118
|
+
/// Returns a Vec of VertexId for required and optional parameters (positional),
|
|
119
|
+
/// which can be used for argument-to-parameter type propagation.
|
|
120
|
+
pub(crate) fn install_parameters(
|
|
121
|
+
genv: &mut GlobalEnv,
|
|
122
|
+
lenv: &mut LocalEnv,
|
|
123
|
+
changes: &mut ChangeSet,
|
|
124
|
+
source: &str,
|
|
125
|
+
params_node: &ruby_prism::ParametersNode,
|
|
126
|
+
) -> Vec<VertexId> {
|
|
127
|
+
let mut param_vtxs = Vec::new();
|
|
128
|
+
|
|
129
|
+
// Required parameters: def foo(a, b)
|
|
130
|
+
for node in params_node.requireds().iter() {
|
|
131
|
+
if let Some(req_param) = node.as_required_parameter_node() {
|
|
132
|
+
let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
|
|
133
|
+
let vtx = install_required_parameter(genv, lenv, name);
|
|
134
|
+
param_vtxs.push(vtx);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Optional parameters: def foo(a = 1, b = "hello")
|
|
139
|
+
for node in params_node.optionals().iter() {
|
|
140
|
+
if let Some(opt_param) = node.as_optional_parameter_node() {
|
|
141
|
+
let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
|
|
142
|
+
let default_value = opt_param.value();
|
|
143
|
+
|
|
144
|
+
let vtx = if let Some(default_vtx) =
|
|
145
|
+
super::install::install_node(genv, lenv, changes, source, &default_value)
|
|
146
|
+
{
|
|
147
|
+
install_optional_parameter(genv, lenv, changes, name, default_vtx)
|
|
148
|
+
} else {
|
|
149
|
+
install_required_parameter(genv, lenv, name)
|
|
150
|
+
};
|
|
151
|
+
param_vtxs.push(vtx);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Rest parameter: def foo(*args)
|
|
156
|
+
// Not included in param_vtxs (variadic args need special handling)
|
|
157
|
+
if let Some(rest_node) = params_node.rest() {
|
|
158
|
+
if let Some(rest_param) = rest_node.as_rest_parameter_node() {
|
|
159
|
+
if let Some(name_id) = rest_param.name() {
|
|
160
|
+
let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
|
|
161
|
+
install_rest_parameter(genv, lenv, name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Keyword rest parameter: def foo(**kwargs)
|
|
167
|
+
// Not included in param_vtxs (keyword args need special handling)
|
|
168
|
+
if let Some(kwrest_node) = params_node.keyword_rest() {
|
|
169
|
+
if let Some(kwrest_param) = kwrest_node.as_keyword_rest_parameter_node() {
|
|
170
|
+
if let Some(name_id) = kwrest_param.name() {
|
|
171
|
+
let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
|
|
172
|
+
install_keyword_rest_parameter(genv, lenv, name);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
param_vtxs
|
|
178
|
+
}
|
|
179
|
+
|
|
116
180
|
#[cfg(test)]
|
|
117
181
|
mod tests {
|
|
118
182
|
use super::*;
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
}
|
|
26
|
+
|
|
27
|
+
#[cfg(test)]
|
|
28
|
+
mod tests {
|
|
29
|
+
use crate::analyzer::install::AstInstaller;
|
|
30
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
31
|
+
use crate::graph::VertexId;
|
|
32
|
+
use crate::parser::ParseSession;
|
|
33
|
+
use crate::types::Type;
|
|
34
|
+
|
|
35
|
+
fn analyze(source: &str) -> GlobalEnv {
|
|
36
|
+
let session = ParseSession::new();
|
|
37
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
38
|
+
let root = parse_result.node();
|
|
39
|
+
let program = root.as_program_node().unwrap();
|
|
40
|
+
|
|
41
|
+
let mut genv = GlobalEnv::new();
|
|
42
|
+
let mut lenv = LocalEnv::new();
|
|
43
|
+
|
|
44
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
45
|
+
for stmt in &program.statements().body() {
|
|
46
|
+
installer.install_node(&stmt);
|
|
47
|
+
}
|
|
48
|
+
installer.finish();
|
|
49
|
+
|
|
50
|
+
genv
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
|
|
54
|
+
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
55
|
+
vertex.show()
|
|
56
|
+
} else if let Some(source) = genv.get_source(vtx) {
|
|
57
|
+
source.ty.show()
|
|
58
|
+
} else {
|
|
59
|
+
panic!("vertex {:?} not found as either Vertex or Source", vtx);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[test]
|
|
64
|
+
fn test_parenthesized_integer() {
|
|
65
|
+
let source = r#"
|
|
66
|
+
class Foo
|
|
67
|
+
def bar
|
|
68
|
+
x = (42)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
"#;
|
|
72
|
+
let genv = analyze(source);
|
|
73
|
+
let info = genv
|
|
74
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
75
|
+
.expect("Foo#bar should be registered");
|
|
76
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
77
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[test]
|
|
81
|
+
fn test_parenthesized_string() {
|
|
82
|
+
let source = r#"
|
|
83
|
+
class Foo
|
|
84
|
+
def bar
|
|
85
|
+
x = ("hello")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
"#;
|
|
89
|
+
let genv = analyze(source);
|
|
90
|
+
let info = genv
|
|
91
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
92
|
+
.expect("Foo#bar should be registered");
|
|
93
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
94
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[test]
|
|
98
|
+
fn test_parenthesized_multiple_statements() {
|
|
99
|
+
let source = r#"
|
|
100
|
+
class Foo
|
|
101
|
+
def bar
|
|
102
|
+
x = (a = 1; "hello")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
"#;
|
|
106
|
+
let genv = analyze(source);
|
|
107
|
+
let info = genv
|
|
108
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
109
|
+
.expect("Foo#bar should be registered");
|
|
110
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
111
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
112
|
+
}
|
|
113
|
+
}
|