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
|
@@ -0,0 +1,521 @@
|
|
|
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
|
+
/// Process a single RescueNode body.
|
|
85
|
+
/// Registers the rescue variable (=> e), processes the body,
|
|
86
|
+
/// then removes the variable from scope.
|
|
87
|
+
fn process_rescue_body(
|
|
88
|
+
genv: &mut GlobalEnv,
|
|
89
|
+
lenv: &mut LocalEnv,
|
|
90
|
+
changes: &mut ChangeSet,
|
|
91
|
+
source: &str,
|
|
92
|
+
rescue_node: &RescueNode,
|
|
93
|
+
) -> Option<VertexId> {
|
|
94
|
+
for exc in &rescue_node.exceptions() {
|
|
95
|
+
install_node(genv, lenv, changes, source, &exc);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Save/restore rescue variable binding (=> e)
|
|
99
|
+
// TODO: Only LocalVariableTargetNode is handled; instance/global/class vars are not yet supported.
|
|
100
|
+
// TODO: Always typed as StandardError regardless of declared exception class.
|
|
101
|
+
let var_binding = if let Some(ref_node) = rescue_node.reference() {
|
|
102
|
+
ref_node.as_local_variable_target_node().map(|target| {
|
|
103
|
+
let name = bytes_to_name(target.name().as_slice());
|
|
104
|
+
let saved = lenv.get_var(&name);
|
|
105
|
+
let exception_vtx = genv.new_vertex();
|
|
106
|
+
let std_err_src = genv.new_source(Type::instance("StandardError"));
|
|
107
|
+
genv.add_edge(std_err_src, exception_vtx);
|
|
108
|
+
lenv.new_var(name.clone(), exception_vtx);
|
|
109
|
+
(name, saved)
|
|
110
|
+
})
|
|
111
|
+
} else {
|
|
112
|
+
None
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
let body_vtx = rescue_node
|
|
116
|
+
.statements()
|
|
117
|
+
.and_then(|s| install_statements(genv, lenv, changes, source, &s));
|
|
118
|
+
|
|
119
|
+
if let Some((name, saved)) = var_binding {
|
|
120
|
+
match saved {
|
|
121
|
+
Some(prev_vtx) => lenv.new_var(name, prev_vtx),
|
|
122
|
+
None => lenv.remove_var(&name),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
body_vtx
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Process RescueModifierNode: `expression rescue rescue_expression`
|
|
130
|
+
pub(crate) fn process_rescue_modifier_node(
|
|
131
|
+
genv: &mut GlobalEnv,
|
|
132
|
+
lenv: &mut LocalEnv,
|
|
133
|
+
changes: &mut ChangeSet,
|
|
134
|
+
source: &str,
|
|
135
|
+
node: &RescueModifierNode,
|
|
136
|
+
) -> Option<VertexId> {
|
|
137
|
+
let result_vtx = genv.new_vertex();
|
|
138
|
+
|
|
139
|
+
let expr_vtx = install_node(genv, lenv, changes, source, &node.expression());
|
|
140
|
+
if let Some(vtx) = expr_vtx {
|
|
141
|
+
genv.add_edge(vtx, result_vtx);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let rescue_vtx = install_node(genv, lenv, changes, source, &node.rescue_expression());
|
|
145
|
+
if let Some(vtx) = rescue_vtx {
|
|
146
|
+
genv.add_edge(vtx, result_vtx);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Some(result_vtx)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[cfg(test)]
|
|
153
|
+
mod tests {
|
|
154
|
+
use crate::analyzer::install::AstInstaller;
|
|
155
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
156
|
+
use crate::graph::VertexId;
|
|
157
|
+
use crate::parser::ParseSession;
|
|
158
|
+
use crate::types::Type;
|
|
159
|
+
|
|
160
|
+
/// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
|
|
161
|
+
fn analyze(source: &str) -> GlobalEnv {
|
|
162
|
+
let session = ParseSession::new();
|
|
163
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
164
|
+
let root = parse_result.node();
|
|
165
|
+
let program = root.as_program_node().unwrap();
|
|
166
|
+
|
|
167
|
+
let mut genv = GlobalEnv::new();
|
|
168
|
+
let mut lenv = LocalEnv::new();
|
|
169
|
+
|
|
170
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
171
|
+
for stmt in &program.statements().body() {
|
|
172
|
+
installer.install_node(&stmt);
|
|
173
|
+
}
|
|
174
|
+
installer.finish();
|
|
175
|
+
|
|
176
|
+
genv
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Helper: get the type string for a vertex ID (checks both Vertex and Source)
|
|
180
|
+
fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
|
|
181
|
+
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
182
|
+
vertex.show()
|
|
183
|
+
} else if let Some(source) = genv.get_source(vtx) {
|
|
184
|
+
source.ty.show()
|
|
185
|
+
} else {
|
|
186
|
+
panic!("vertex {:?} not found as either Vertex or Source", vtx);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#[test]
|
|
191
|
+
fn test_begin_rescue_basic() {
|
|
192
|
+
let source = r#"
|
|
193
|
+
class Foo
|
|
194
|
+
def bar
|
|
195
|
+
begin
|
|
196
|
+
"hello"
|
|
197
|
+
rescue
|
|
198
|
+
42
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
"#;
|
|
203
|
+
let genv = analyze(source);
|
|
204
|
+
let info = genv
|
|
205
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
206
|
+
.expect("Foo#bar should be registered");
|
|
207
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
208
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#[test]
|
|
212
|
+
fn test_begin_rescue_else() {
|
|
213
|
+
let source = r#"
|
|
214
|
+
class Foo
|
|
215
|
+
def bar
|
|
216
|
+
begin
|
|
217
|
+
"hello"
|
|
218
|
+
rescue
|
|
219
|
+
42
|
|
220
|
+
else
|
|
221
|
+
:ok
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
"#;
|
|
226
|
+
let genv = analyze(source);
|
|
227
|
+
let info = genv
|
|
228
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
229
|
+
.expect("Foo#bar should be registered");
|
|
230
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
231
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
232
|
+
// else present: begin body excluded, else + rescue types
|
|
233
|
+
assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
|
|
234
|
+
assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
|
|
235
|
+
assert!(!type_str.contains("String"), "should NOT contain String: {}", type_str);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[test]
|
|
239
|
+
fn test_begin_ensure_only() {
|
|
240
|
+
let source = r#"
|
|
241
|
+
class Foo
|
|
242
|
+
def bar
|
|
243
|
+
begin
|
|
244
|
+
"hello"
|
|
245
|
+
ensure
|
|
246
|
+
puts "cleanup"
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
"#;
|
|
251
|
+
let genv = analyze(source);
|
|
252
|
+
let info = genv
|
|
253
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
254
|
+
.expect("Foo#bar should be registered");
|
|
255
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
256
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn test_begin_rescue_ensure() {
|
|
261
|
+
let source = r#"
|
|
262
|
+
class Foo
|
|
263
|
+
def bar
|
|
264
|
+
begin
|
|
265
|
+
"hello"
|
|
266
|
+
rescue
|
|
267
|
+
42
|
|
268
|
+
ensure
|
|
269
|
+
:cleanup
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
"#;
|
|
274
|
+
let genv = analyze(source);
|
|
275
|
+
let info = genv
|
|
276
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
277
|
+
.expect("Foo#bar should be registered");
|
|
278
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
279
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
280
|
+
// ensure does not affect return type
|
|
281
|
+
assert_eq!(type_str, "(Integer | String)");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[test]
|
|
285
|
+
fn test_rescue_variable_type() {
|
|
286
|
+
let source = r#"
|
|
287
|
+
class Foo
|
|
288
|
+
def bar
|
|
289
|
+
begin
|
|
290
|
+
"hello"
|
|
291
|
+
rescue => e
|
|
292
|
+
e
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
"#;
|
|
297
|
+
let genv = analyze(source);
|
|
298
|
+
let info = genv
|
|
299
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
300
|
+
.expect("Foo#bar should be registered");
|
|
301
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
302
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
303
|
+
assert!(
|
|
304
|
+
type_str.contains("StandardError"),
|
|
305
|
+
"should contain StandardError: {}",
|
|
306
|
+
type_str
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#[test]
|
|
311
|
+
fn test_multiple_rescue_clauses() {
|
|
312
|
+
let source = r#"
|
|
313
|
+
class Foo
|
|
314
|
+
def bar
|
|
315
|
+
begin
|
|
316
|
+
"hello"
|
|
317
|
+
rescue ArgumentError
|
|
318
|
+
42
|
|
319
|
+
rescue RuntimeError
|
|
320
|
+
:error
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
"#;
|
|
325
|
+
let genv = analyze(source);
|
|
326
|
+
let info = genv
|
|
327
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
328
|
+
.expect("Foo#bar should be registered");
|
|
329
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
330
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
331
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
332
|
+
assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
|
|
333
|
+
assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
#[test]
|
|
337
|
+
fn test_rescue_modifier_basic() {
|
|
338
|
+
let source = r#"
|
|
339
|
+
class Foo
|
|
340
|
+
def bar
|
|
341
|
+
"hello" rescue 42
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
"#;
|
|
345
|
+
let genv = analyze(source);
|
|
346
|
+
let info = genv
|
|
347
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
348
|
+
.expect("Foo#bar should be registered");
|
|
349
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
350
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
#[test]
|
|
354
|
+
fn test_rescue_modifier_same_type() {
|
|
355
|
+
let source = r#"
|
|
356
|
+
class Foo
|
|
357
|
+
def bar
|
|
358
|
+
"hello" rescue "world"
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
"#;
|
|
362
|
+
let genv = analyze(source);
|
|
363
|
+
let info = genv
|
|
364
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
365
|
+
.expect("Foo#bar should be registered");
|
|
366
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
367
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#[test]
|
|
371
|
+
fn test_nested_begin_rescue() {
|
|
372
|
+
let source = r#"
|
|
373
|
+
class Foo
|
|
374
|
+
def bar
|
|
375
|
+
begin
|
|
376
|
+
begin
|
|
377
|
+
"inner"
|
|
378
|
+
rescue
|
|
379
|
+
42
|
|
380
|
+
end
|
|
381
|
+
rescue
|
|
382
|
+
:outer
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
"#;
|
|
387
|
+
let genv = analyze(source);
|
|
388
|
+
let info = genv
|
|
389
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
390
|
+
.expect("Foo#bar should be registered");
|
|
391
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
392
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
393
|
+
// Outer: Union(inner_begin_rescue | :outer) = (Integer | String | Symbol)
|
|
394
|
+
assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
|
|
395
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
396
|
+
assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
#[test]
|
|
400
|
+
fn test_begin_rescue_in_method() {
|
|
401
|
+
let source = r#"
|
|
402
|
+
class Foo
|
|
403
|
+
def bar
|
|
404
|
+
x = begin
|
|
405
|
+
"hello"
|
|
406
|
+
rescue
|
|
407
|
+
42
|
|
408
|
+
end
|
|
409
|
+
x
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
"#;
|
|
413
|
+
let genv = analyze(source);
|
|
414
|
+
let info = genv
|
|
415
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
416
|
+
.expect("Foo#bar should be registered");
|
|
417
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
418
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
#[test]
|
|
422
|
+
fn test_ensure_side_effects() {
|
|
423
|
+
// ensure body should be processed (no panic) but not affect return type
|
|
424
|
+
let source = r#"
|
|
425
|
+
class Foo
|
|
426
|
+
def bar
|
|
427
|
+
begin
|
|
428
|
+
"hello"
|
|
429
|
+
rescue
|
|
430
|
+
42
|
|
431
|
+
ensure
|
|
432
|
+
x = "side_effect"
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
"#;
|
|
437
|
+
let genv = analyze(source);
|
|
438
|
+
let info = genv
|
|
439
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
440
|
+
.expect("Foo#bar should be registered");
|
|
441
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
442
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
#[test]
|
|
446
|
+
fn test_rescue_variable_scope_restore() {
|
|
447
|
+
// Rescue variable should not destroy outer binding
|
|
448
|
+
let source = r#"
|
|
449
|
+
class Foo
|
|
450
|
+
def bar
|
|
451
|
+
e = "outer"
|
|
452
|
+
begin
|
|
453
|
+
"hello"
|
|
454
|
+
rescue => e
|
|
455
|
+
e
|
|
456
|
+
end
|
|
457
|
+
e
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
"#;
|
|
461
|
+
let genv = analyze(source);
|
|
462
|
+
let info = genv
|
|
463
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
464
|
+
.expect("Foo#bar should be registered");
|
|
465
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
466
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
467
|
+
// After rescue block, e should be restored to outer binding (String)
|
|
468
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
#[test]
|
|
472
|
+
fn test_rescue_variable_scope_removal() {
|
|
473
|
+
// When rescue variable has no prior binding, it should be removed after rescue block
|
|
474
|
+
let source = r#"
|
|
475
|
+
class Foo
|
|
476
|
+
def bar
|
|
477
|
+
begin
|
|
478
|
+
"hello"
|
|
479
|
+
rescue => e
|
|
480
|
+
e
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
"#;
|
|
485
|
+
let genv = analyze(source);
|
|
486
|
+
let info = genv
|
|
487
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
488
|
+
.expect("Foo#bar should be registered");
|
|
489
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
490
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
491
|
+
// begin body (String) + rescue body where e = StandardError
|
|
492
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
493
|
+
assert!(
|
|
494
|
+
type_str.contains("StandardError"),
|
|
495
|
+
"should contain StandardError: {}",
|
|
496
|
+
type_str
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
#[test]
|
|
501
|
+
fn test_empty_rescue_body_is_nil() {
|
|
502
|
+
let source = r#"
|
|
503
|
+
class Foo
|
|
504
|
+
def bar
|
|
505
|
+
begin
|
|
506
|
+
"hello"
|
|
507
|
+
rescue
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
"#;
|
|
512
|
+
let genv = analyze(source);
|
|
513
|
+
let info = genv
|
|
514
|
+
.resolve_method(&Type::instance("Foo"), "bar")
|
|
515
|
+
.expect("Foo#bar should be registered");
|
|
516
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
517
|
+
let type_str = get_type_show(&genv, ret_vtx);
|
|
518
|
+
assert!(type_str.contains("String"), "should contain String: {}", type_str);
|
|
519
|
+
assert!(type_str.contains("nil"), "should contain nil: {}", type_str);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
@@ -8,13 +8,16 @@ use crate::env::{GlobalEnv, LocalEnv};
|
|
|
8
8
|
use crate::graph::{ChangeSet, VertexId};
|
|
9
9
|
use ruby_prism::Node;
|
|
10
10
|
|
|
11
|
+
use super::assignments::process_multi_write_node;
|
|
11
12
|
use super::blocks::process_block_node;
|
|
12
13
|
use super::conditionals::{process_case_node, process_if_node, process_unless_node};
|
|
13
14
|
use super::definitions::{process_class_node, process_def_node, process_module_node};
|
|
15
|
+
use super::exceptions::{process_begin_node, process_rescue_modifier_node};
|
|
14
16
|
use super::dispatch::{dispatch_needs_child, dispatch_simple, process_needs_child, DispatchResult};
|
|
15
17
|
use super::literals::install_literal_node;
|
|
16
|
-
use super::
|
|
18
|
+
use super::loops::{process_until_node, process_while_node};
|
|
17
19
|
use super::operators::{process_and_node, process_or_node};
|
|
20
|
+
use super::parentheses::process_parentheses_node;
|
|
18
21
|
use super::returns::process_return_node;
|
|
19
22
|
|
|
20
23
|
/// Build graph from AST (public API wrapper)
|
|
@@ -79,6 +82,20 @@ pub(crate) fn install_node(
|
|
|
79
82
|
return process_case_node(genv, lenv, changes, source, &case_node);
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
if let Some(begin_node) = node.as_begin_node() {
|
|
86
|
+
return process_begin_node(genv, lenv, changes, source, &begin_node);
|
|
87
|
+
}
|
|
88
|
+
if let Some(rescue_modifier) = node.as_rescue_modifier_node() {
|
|
89
|
+
return process_rescue_modifier_node(genv, lenv, changes, source, &rescue_modifier);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if let Some(while_node) = node.as_while_node() {
|
|
93
|
+
return process_while_node(genv, lenv, changes, source, &while_node);
|
|
94
|
+
}
|
|
95
|
+
if let Some(until_node) = node.as_until_node() {
|
|
96
|
+
return process_until_node(genv, lenv, changes, source, &until_node);
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
if let Some(paren_node) = node.as_parentheses_node() {
|
|
83
100
|
return process_parentheses_node(genv, lenv, changes, source, &paren_node);
|
|
84
101
|
}
|
|
@@ -94,6 +111,10 @@ pub(crate) fn install_node(
|
|
|
94
111
|
return process_or_node(genv, lenv, changes, source, &or_node);
|
|
95
112
|
}
|
|
96
113
|
|
|
114
|
+
if let Some(multi_write) = node.as_multi_write_node() {
|
|
115
|
+
return process_multi_write_node(genv, lenv, changes, source, &multi_write);
|
|
116
|
+
}
|
|
117
|
+
|
|
97
118
|
match dispatch_simple(genv, lenv, node) {
|
|
98
119
|
DispatchResult::Vertex(vtx) => return Some(vtx),
|
|
99
120
|
DispatchResult::NotHandled => {}
|