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
|
@@ -1,284 +0,0 @@
|
|
|
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
|
-
}
|
|
80
|
-
|
|
81
|
-
#[cfg(test)]
|
|
82
|
-
mod tests {
|
|
83
|
-
use crate::analyzer::install::AstInstaller;
|
|
84
|
-
use crate::env::{GlobalEnv, LocalEnv};
|
|
85
|
-
use crate::graph::VertexId;
|
|
86
|
-
use crate::parser::ParseSession;
|
|
87
|
-
use crate::types::Type;
|
|
88
|
-
|
|
89
|
-
/// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
|
|
90
|
-
fn analyze(source: &str) -> GlobalEnv {
|
|
91
|
-
let session = ParseSession::new();
|
|
92
|
-
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
93
|
-
let root = parse_result.node();
|
|
94
|
-
let program = root.as_program_node().unwrap();
|
|
95
|
-
|
|
96
|
-
let mut genv = GlobalEnv::new();
|
|
97
|
-
let mut lenv = LocalEnv::new();
|
|
98
|
-
|
|
99
|
-
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
100
|
-
for stmt in &program.statements().body() {
|
|
101
|
-
installer.install_node(&stmt);
|
|
102
|
-
}
|
|
103
|
-
installer.finish();
|
|
104
|
-
|
|
105
|
-
genv
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/// Helper: get the type string for a vertex ID
|
|
109
|
-
fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
|
|
110
|
-
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
111
|
-
vertex.show()
|
|
112
|
-
} else if let Some(source) = genv.get_source(vtx) {
|
|
113
|
-
source.ty.show()
|
|
114
|
-
} else {
|
|
115
|
-
panic!("vertex {:?} not found as either Vertex or Source", vtx);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
#[test]
|
|
120
|
-
fn test_and_node_union_type() {
|
|
121
|
-
let source = r#"
|
|
122
|
-
class Foo
|
|
123
|
-
def bar
|
|
124
|
-
true && "hello"
|
|
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
|
-
let type_str = get_type_show(&genv, ret_vtx);
|
|
134
|
-
assert!(type_str.contains("TrueClass"), "should contain TrueClass: {}", type_str);
|
|
135
|
-
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
#[test]
|
|
139
|
-
fn test_and_node_same_type() {
|
|
140
|
-
let source = r#"
|
|
141
|
-
class Foo
|
|
142
|
-
def bar
|
|
143
|
-
"a" && "b"
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
"#;
|
|
147
|
-
let genv = analyze(source);
|
|
148
|
-
let info = genv
|
|
149
|
-
.resolve_method(&Type::instance("Foo"), "bar")
|
|
150
|
-
.expect("Foo#bar should be registered");
|
|
151
|
-
let ret_vtx = info.return_vertex.unwrap();
|
|
152
|
-
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
#[test]
|
|
156
|
-
fn test_or_node_union_type() {
|
|
157
|
-
let source = r#"
|
|
158
|
-
class Foo
|
|
159
|
-
def bar
|
|
160
|
-
42 || "hello"
|
|
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
|
-
let type_str = get_type_show(&genv, ret_vtx);
|
|
170
|
-
assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
|
|
171
|
-
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
#[test]
|
|
175
|
-
fn test_or_node_same_type() {
|
|
176
|
-
let source = r#"
|
|
177
|
-
class Foo
|
|
178
|
-
def bar
|
|
179
|
-
1 || 2
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
"#;
|
|
183
|
-
let genv = analyze(source);
|
|
184
|
-
let info = genv
|
|
185
|
-
.resolve_method(&Type::instance("Foo"), "bar")
|
|
186
|
-
.expect("Foo#bar should be registered");
|
|
187
|
-
let ret_vtx = info.return_vertex.unwrap();
|
|
188
|
-
assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
#[test]
|
|
192
|
-
fn test_nested_logical_operators() {
|
|
193
|
-
let source = r#"
|
|
194
|
-
class Foo
|
|
195
|
-
def bar
|
|
196
|
-
1 && "a" || :b
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
"#;
|
|
200
|
-
let genv = analyze(source);
|
|
201
|
-
let info = genv
|
|
202
|
-
.resolve_method(&Type::instance("Foo"), "bar")
|
|
203
|
-
.expect("Foo#bar should be registered");
|
|
204
|
-
let ret_vtx = info.return_vertex.unwrap();
|
|
205
|
-
let type_str = get_type_show(&genv, ret_vtx);
|
|
206
|
-
assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
|
|
207
|
-
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
208
|
-
assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// ============================================
|
|
212
|
-
// Not operator (!) tests
|
|
213
|
-
// ============================================
|
|
214
|
-
|
|
215
|
-
#[test]
|
|
216
|
-
fn test_not_operator_returns_boolean_union() {
|
|
217
|
-
let source = r#"
|
|
218
|
-
class Foo
|
|
219
|
-
def bar
|
|
220
|
-
!true
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
"#;
|
|
224
|
-
let genv = analyze(source);
|
|
225
|
-
let info = genv
|
|
226
|
-
.resolve_method(&Type::instance("Foo"), "bar")
|
|
227
|
-
.expect("bar should be registered");
|
|
228
|
-
let ty = get_type_show(&genv, info.return_vertex.unwrap());
|
|
229
|
-
assert!(ty.contains("TrueClass"), "expected TrueClass in {}", ty);
|
|
230
|
-
assert!(ty.contains("FalseClass"), "expected FalseClass in {}", ty);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
#[test]
|
|
234
|
-
fn test_not_operator_receiver_side_effects_analyzed() {
|
|
235
|
-
let source = r#"
|
|
236
|
-
class Foo
|
|
237
|
-
def bar
|
|
238
|
-
!(1.upcase)
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
"#;
|
|
242
|
-
let genv = analyze(source);
|
|
243
|
-
assert!(
|
|
244
|
-
!genv.type_errors.is_empty(),
|
|
245
|
-
"expected type error for Integer#upcase"
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
#[test]
|
|
250
|
-
fn test_double_not_operator_union() {
|
|
251
|
-
let source = r#"
|
|
252
|
-
class Foo
|
|
253
|
-
def bar
|
|
254
|
-
!!true
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
"#;
|
|
258
|
-
let genv = analyze(source);
|
|
259
|
-
let info = genv
|
|
260
|
-
.resolve_method(&Type::instance("Foo"), "bar")
|
|
261
|
-
.expect("bar should be registered");
|
|
262
|
-
let ty = get_type_show(&genv, info.return_vertex.unwrap());
|
|
263
|
-
assert!(ty.contains("TrueClass"), "expected TrueClass in {}", ty);
|
|
264
|
-
assert!(ty.contains("FalseClass"), "expected FalseClass in {}", ty);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
#[test]
|
|
268
|
-
fn test_not_nil_returns_boolean() {
|
|
269
|
-
let source = r#"
|
|
270
|
-
class Foo
|
|
271
|
-
def bar
|
|
272
|
-
!nil
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
"#;
|
|
276
|
-
let genv = analyze(source);
|
|
277
|
-
let info = genv
|
|
278
|
-
.resolve_method(&Type::instance("Foo"), "bar")
|
|
279
|
-
.expect("bar should be registered");
|
|
280
|
-
let ty = get_type_show(&genv, info.return_vertex.unwrap());
|
|
281
|
-
assert!(ty.contains("TrueClass"), "expected TrueClass in {}", ty);
|
|
282
|
-
assert!(ty.contains("FalseClass"), "expected FalseClass in {}", ty);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
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
|
-
}
|
|
40
|
-
|
|
41
|
-
#[cfg(test)]
|
|
42
|
-
mod tests {
|
|
43
|
-
use crate::env::{GlobalEnv, LocalEnv};
|
|
44
|
-
use crate::graph::ChangeSet;
|
|
45
|
-
use crate::parser::ParseSession;
|
|
46
|
-
use crate::types::Type;
|
|
47
|
-
|
|
48
|
-
fn setup_and_infer(source: &str) -> GlobalEnv {
|
|
49
|
-
let session = ParseSession::new();
|
|
50
|
-
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
51
|
-
let root = parse_result.node();
|
|
52
|
-
let program = root.as_program_node().unwrap();
|
|
53
|
-
|
|
54
|
-
let mut genv = GlobalEnv::new();
|
|
55
|
-
let mut lenv = LocalEnv::new();
|
|
56
|
-
let mut changes = ChangeSet::new();
|
|
57
|
-
|
|
58
|
-
for stmt in &program.statements().body() {
|
|
59
|
-
crate::analyzer::install::install_node(
|
|
60
|
-
&mut genv, &mut lenv, &mut changes, source, &stmt,
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
genv.apply_changes(changes);
|
|
65
|
-
genv.run_all();
|
|
66
|
-
genv
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
fn get_return_type(genv: &GlobalEnv, class_name: &str, method_name: &str) -> String {
|
|
70
|
-
let info = genv
|
|
71
|
-
.resolve_method(&Type::instance(class_name), method_name)
|
|
72
|
-
.unwrap_or_else(|| panic!("{}#{} should be registered", class_name, method_name));
|
|
73
|
-
let vtx = info
|
|
74
|
-
.return_vertex
|
|
75
|
-
.expect("return_vertex should be Some");
|
|
76
|
-
|
|
77
|
-
if let Some(source) = genv.get_source(vtx) {
|
|
78
|
-
source.ty.show()
|
|
79
|
-
} else if let Some(vertex) = genv.get_vertex(vtx) {
|
|
80
|
-
vertex.show()
|
|
81
|
-
} else {
|
|
82
|
-
panic!("return_vertex not found");
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
#[test]
|
|
87
|
-
fn test_simple_return() {
|
|
88
|
-
let source = r#"
|
|
89
|
-
class Foo
|
|
90
|
-
def bar
|
|
91
|
-
return "hello"
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
"#;
|
|
95
|
-
let genv = setup_and_infer(source);
|
|
96
|
-
assert_eq!(get_return_type(&genv, "Foo", "bar"), "String");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
#[test]
|
|
100
|
-
fn test_return_with_implicit_return_union() {
|
|
101
|
-
let source = r#"
|
|
102
|
-
class Foo
|
|
103
|
-
def bar
|
|
104
|
-
return "hello" if true
|
|
105
|
-
42
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
"#;
|
|
109
|
-
let genv = setup_and_infer(source);
|
|
110
|
-
let ty = get_return_type(&genv, "Foo", "bar");
|
|
111
|
-
assert!(ty.contains("Integer"), "should contain Integer, got: {}", ty);
|
|
112
|
-
assert!(ty.contains("String"), "should contain String, got: {}", ty);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#[test]
|
|
116
|
-
fn test_multiple_returns() {
|
|
117
|
-
let source = r#"
|
|
118
|
-
class Foo
|
|
119
|
-
def bar
|
|
120
|
-
return "a" if true
|
|
121
|
-
return :b if false
|
|
122
|
-
42
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
"#;
|
|
126
|
-
let genv = setup_and_infer(source);
|
|
127
|
-
let ty = get_return_type(&genv, "Foo", "bar");
|
|
128
|
-
assert!(ty.contains("Integer"), "should contain Integer, got: {}", ty);
|
|
129
|
-
assert!(ty.contains("String"), "should contain String, got: {}", ty);
|
|
130
|
-
assert!(ty.contains("Symbol"), "should contain Symbol, got: {}", ty);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
#[test]
|
|
134
|
-
fn test_return_without_value() {
|
|
135
|
-
let source = r#"
|
|
136
|
-
class Foo
|
|
137
|
-
def bar
|
|
138
|
-
return if true
|
|
139
|
-
42
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
"#;
|
|
143
|
-
let genv = setup_and_infer(source);
|
|
144
|
-
let ty = get_return_type(&genv, "Foo", "bar");
|
|
145
|
-
assert!(ty.contains("Integer"), "should contain Integer, got: {}", ty);
|
|
146
|
-
assert!(ty.contains("nil"), "should contain nil, got: {}", ty);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
#[test]
|
|
150
|
-
fn test_no_return_backward_compat() {
|
|
151
|
-
let source = r#"
|
|
152
|
-
class Foo
|
|
153
|
-
def bar
|
|
154
|
-
"hello"
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
"#;
|
|
158
|
-
let genv = setup_and_infer(source);
|
|
159
|
-
assert_eq!(get_return_type(&genv, "Foo", "bar"), "String");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
#[test]
|
|
163
|
-
fn test_return_only_method() {
|
|
164
|
-
let source = r#"
|
|
165
|
-
class Foo
|
|
166
|
-
def bar
|
|
167
|
-
return "hello"
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
"#;
|
|
171
|
-
let genv = setup_and_infer(source);
|
|
172
|
-
assert_eq!(get_return_type(&genv, "Foo", "bar"), "String");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
#[test]
|
|
176
|
-
fn test_return_dead_code_over_approximation() {
|
|
177
|
-
let source = r#"
|
|
178
|
-
class Foo
|
|
179
|
-
def bar
|
|
180
|
-
return "hello"
|
|
181
|
-
42
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
"#;
|
|
185
|
-
let genv = setup_and_infer(source);
|
|
186
|
-
let ty = get_return_type(&genv, "Foo", "bar");
|
|
187
|
-
// Dead code after return is still processed (over-approximation)
|
|
188
|
-
assert!(ty.contains("Integer"), "should contain Integer (dead code), got: {}", ty);
|
|
189
|
-
assert!(ty.contains("String"), "should contain String, got: {}", ty);
|
|
190
|
-
}
|
|
191
|
-
}
|
data/rust/src/env/local_env.rs
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
use crate::graph::VertexId;
|
|
2
|
-
use std::collections::HashMap;
|
|
3
|
-
|
|
4
|
-
/// Local environment: mapping of local variable names to VertexIDs
|
|
5
|
-
pub struct LocalEnv {
|
|
6
|
-
locals: HashMap<String, VertexId>,
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
impl Default for LocalEnv {
|
|
10
|
-
fn default() -> Self {
|
|
11
|
-
Self::new()
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
impl LocalEnv {
|
|
16
|
-
pub fn new() -> Self {
|
|
17
|
-
Self {
|
|
18
|
-
locals: HashMap::new(),
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/// Register variable
|
|
23
|
-
pub fn new_var(&mut self, name: String, vtx_id: VertexId) {
|
|
24
|
-
self.locals.insert(name, vtx_id);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/// Get variable
|
|
28
|
-
pub fn get_var(&self, name: &str) -> Option<VertexId> {
|
|
29
|
-
self.locals.get(name).copied()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/// Remove a variable from the local environment.
|
|
33
|
-
/// Used for scoped variables like rescue's `=> e` binding.
|
|
34
|
-
pub fn remove_var(&mut self, name: &str) {
|
|
35
|
-
self.locals.remove(name);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/// Get all variables
|
|
39
|
-
pub fn all_vars(&self) -> impl Iterator<Item = (&String, &VertexId)> {
|
|
40
|
-
self.locals.iter()
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
#[cfg(test)]
|
|
45
|
-
mod tests {
|
|
46
|
-
use super::*;
|
|
47
|
-
|
|
48
|
-
#[test]
|
|
49
|
-
fn test_local_env_default() {
|
|
50
|
-
let lenv = LocalEnv::default();
|
|
51
|
-
assert_eq!(lenv.get_var("x"), None);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#[test]
|
|
55
|
-
fn test_local_env() {
|
|
56
|
-
let mut lenv = LocalEnv::new();
|
|
57
|
-
|
|
58
|
-
lenv.new_var("x".to_string(), VertexId(1));
|
|
59
|
-
lenv.new_var("y".to_string(), VertexId(2));
|
|
60
|
-
|
|
61
|
-
assert_eq!(lenv.get_var("x"), Some(VertexId(1)));
|
|
62
|
-
assert_eq!(lenv.get_var("y"), Some(VertexId(2)));
|
|
63
|
-
assert_eq!(lenv.get_var("z"), None);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
#[test]
|
|
67
|
-
fn test_local_env_remove_var() {
|
|
68
|
-
let mut lenv = LocalEnv::new();
|
|
69
|
-
|
|
70
|
-
lenv.new_var("e".to_string(), VertexId(1));
|
|
71
|
-
assert_eq!(lenv.get_var("e"), Some(VertexId(1)));
|
|
72
|
-
|
|
73
|
-
lenv.remove_var("e");
|
|
74
|
-
assert_eq!(lenv.get_var("e"), None);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
#[test]
|
|
78
|
-
fn test_local_env_remove_nonexistent() {
|
|
79
|
-
let mut lenv = LocalEnv::new();
|
|
80
|
-
lenv.remove_var("x"); // should not panic
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#[test]
|
|
84
|
-
fn test_local_env_override() {
|
|
85
|
-
let mut lenv = LocalEnv::new();
|
|
86
|
-
|
|
87
|
-
lenv.new_var("x".to_string(), VertexId(1));
|
|
88
|
-
lenv.new_var("x".to_string(), VertexId(2)); // Override
|
|
89
|
-
|
|
90
|
-
assert_eq!(lenv.get_var("x"), Some(VertexId(2)));
|
|
91
|
-
}
|
|
92
|
-
}
|