method-ray 0.1.10 → 0.2.0
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 +22 -0
- data/core/Cargo.toml +1 -1
- data/core/src/analyzer/blocks.rs +1 -1
- data/core/src/analyzer/compound_assignments.rs +194 -0
- data/core/src/analyzer/conditionals.rs +268 -2
- data/core/src/analyzer/dispatch.rs +234 -22
- data/core/src/analyzer/install.rs +36 -1
- data/core/src/analyzer/lambdas.rs +153 -0
- data/core/src/analyzer/literals.rs +11 -0
- data/core/src/analyzer/mod.rs +3 -0
- data/core/src/analyzer/variables.rs +224 -0
- data/core/src/analyzer/yields.rs +24 -0
- data/core/src/env/global_env.rs +50 -0
- data/core/src/env/scope.rs +91 -7
- data/core/src/graph/box.rs +11 -0
- data/core/src/types.rs +34 -0
- data/ext/Cargo.toml +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +4 -1
|
@@ -13,9 +13,11 @@ use ruby_prism::Node;
|
|
|
13
13
|
|
|
14
14
|
use super::bytes_to_name;
|
|
15
15
|
use super::calls::install_method_call;
|
|
16
|
+
use super::compound_assignments::{CompoundOp, CompoundVarKind};
|
|
16
17
|
use super::variables::{
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
install_class_var_read, install_class_var_write, install_constant_read, install_constant_write,
|
|
19
|
+
install_global_var_read, install_global_var_write, install_ivar_read, install_ivar_write,
|
|
20
|
+
install_local_var_read, install_local_var_write, install_self,
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
/// Collect positional and keyword arguments from AST argument nodes.
|
|
@@ -75,10 +77,22 @@ pub(crate) enum DispatchResult {
|
|
|
75
77
|
|
|
76
78
|
/// Kind of child processing needed
|
|
77
79
|
pub(crate) enum NeedsChildKind<'a> {
|
|
78
|
-
/// Instance variable write: need to process value, then call
|
|
80
|
+
/// Instance variable write: need to process value, then call install_ivar_write
|
|
79
81
|
IvarWrite { ivar_name: String, value: Node<'a> },
|
|
80
|
-
///
|
|
82
|
+
/// Class variable write: need to process value, then call install_class_var_write
|
|
83
|
+
ClassVarWrite { cvar_name: String, value: Node<'a> },
|
|
84
|
+
/// Local variable write: need to process value, then call install_local_var_write
|
|
81
85
|
LocalVarWrite { var_name: String, value: Node<'a> },
|
|
86
|
+
/// Global variable write: need to process value, then call install_global_var_write
|
|
87
|
+
GlobalVarWrite { gvar_name: String, value: Node<'a> },
|
|
88
|
+
ConstantWrite { const_name: String, value: Node<'a> },
|
|
89
|
+
CompoundWrite {
|
|
90
|
+
var_kind: CompoundVarKind,
|
|
91
|
+
op: CompoundOp,
|
|
92
|
+
value: Node<'a>,
|
|
93
|
+
},
|
|
94
|
+
/// Proc/lambda literals
|
|
95
|
+
ProcLiteral { block: Node<'a> },
|
|
82
96
|
/// Method call: need to process receiver, then call finish_method_call
|
|
83
97
|
MethodCall {
|
|
84
98
|
receiver: Node<'a>,
|
|
@@ -131,6 +145,33 @@ pub(crate) fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &
|
|
|
131
145
|
};
|
|
132
146
|
}
|
|
133
147
|
|
|
148
|
+
// Class variable read: @@name
|
|
149
|
+
// Ruby: uninitialized class variables raise NameError at runtime.
|
|
150
|
+
// We fall back to nil so downstream method calls are still type-checked
|
|
151
|
+
// rather than silently skipped. This may produce nil-union types when
|
|
152
|
+
// reads precede writes in source order.
|
|
153
|
+
if let Some(cvar_read) = node.as_class_variable_read_node() {
|
|
154
|
+
let cvar_name = bytes_to_name(cvar_read.name().as_slice());
|
|
155
|
+
let vtx = install_class_var_read(genv, &cvar_name).unwrap_or_else(|| {
|
|
156
|
+
let nil_vtx = genv.new_source(Type::Nil);
|
|
157
|
+
install_class_var_write(genv, cvar_name, nil_vtx)
|
|
158
|
+
});
|
|
159
|
+
return DispatchResult::Vertex(vtx);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Global variable read: $name
|
|
163
|
+
// Ruby: uninitialized global variables are nil.
|
|
164
|
+
// Register the nil vertex so repeated reads of the same uninitialized
|
|
165
|
+
// variable reuse one vertex instead of allocating a new Source each time.
|
|
166
|
+
if let Some(gvar_read) = node.as_global_variable_read_node() {
|
|
167
|
+
let gvar_name = bytes_to_name(gvar_read.name().as_slice());
|
|
168
|
+
let vtx = install_global_var_read(genv, &gvar_name).unwrap_or_else(|| {
|
|
169
|
+
let nil_vtx = genv.new_source(Type::Nil);
|
|
170
|
+
install_global_var_write(genv, gvar_name, nil_vtx)
|
|
171
|
+
});
|
|
172
|
+
return DispatchResult::Vertex(vtx);
|
|
173
|
+
}
|
|
174
|
+
|
|
134
175
|
// self
|
|
135
176
|
if node.as_self_node().is_some() {
|
|
136
177
|
return DispatchResult::Vertex(install_self(genv));
|
|
@@ -148,6 +189,11 @@ pub(crate) fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &
|
|
|
148
189
|
// ConstantReadNode: User → Type::Singleton("User") or Type::Singleton("Api::User")
|
|
149
190
|
if let Some(const_read) = node.as_constant_read_node() {
|
|
150
191
|
let name = bytes_to_name(const_read.name().as_slice());
|
|
192
|
+
|
|
193
|
+
if let Some(vtx) = install_constant_read(genv, &name) {
|
|
194
|
+
return DispatchResult::Vertex(vtx);
|
|
195
|
+
}
|
|
196
|
+
|
|
151
197
|
let resolved_name = genv.scope_manager.lookup_constant(&name)
|
|
152
198
|
.unwrap_or(name);
|
|
153
199
|
let vtx = genv.new_source(Type::singleton(&resolved_name));
|
|
@@ -162,6 +208,16 @@ pub(crate) fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &
|
|
|
162
208
|
}
|
|
163
209
|
}
|
|
164
210
|
|
|
211
|
+
// defined?(expr) → String | nil (child expression is NOT evaluated)
|
|
212
|
+
if node.as_defined_node().is_some() {
|
|
213
|
+
let result_vtx = genv.new_vertex();
|
|
214
|
+
let str_vtx = genv.new_source(Type::string());
|
|
215
|
+
let nil_vtx = genv.new_source(Type::Nil);
|
|
216
|
+
genv.add_edge(str_vtx, result_vtx);
|
|
217
|
+
genv.add_edge(nil_vtx, result_vtx);
|
|
218
|
+
return DispatchResult::Vertex(result_vtx);
|
|
219
|
+
}
|
|
220
|
+
|
|
165
221
|
DispatchResult::NotHandled
|
|
166
222
|
}
|
|
167
223
|
|
|
@@ -206,6 +262,32 @@ pub(crate) fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<
|
|
|
206
262
|
});
|
|
207
263
|
}
|
|
208
264
|
|
|
265
|
+
// Class variable write: @@name = value
|
|
266
|
+
if let Some(cvar_write) = node.as_class_variable_write_node() {
|
|
267
|
+
let cvar_name = bytes_to_name(cvar_write.name().as_slice());
|
|
268
|
+
return Some(NeedsChildKind::ClassVarWrite {
|
|
269
|
+
cvar_name,
|
|
270
|
+
value: cvar_write.value(),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Global variable write: $name = value
|
|
275
|
+
if let Some(gvar_write) = node.as_global_variable_write_node() {
|
|
276
|
+
let gvar_name = bytes_to_name(gvar_write.name().as_slice());
|
|
277
|
+
return Some(NeedsChildKind::GlobalVarWrite {
|
|
278
|
+
gvar_name,
|
|
279
|
+
value: gvar_write.value(),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if let Some(const_write) = node.as_constant_write_node() {
|
|
284
|
+
let const_name = bytes_to_name(const_write.name().as_slice());
|
|
285
|
+
return Some(NeedsChildKind::ConstantWrite {
|
|
286
|
+
const_name,
|
|
287
|
+
value: const_write.value(),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
209
291
|
// Local variable write: x = value
|
|
210
292
|
if let Some(write_node) = node.as_local_variable_write_node() {
|
|
211
293
|
let var_name = bytes_to_name(write_node.name().as_slice());
|
|
@@ -215,6 +297,46 @@ pub(crate) fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<
|
|
|
215
297
|
});
|
|
216
298
|
}
|
|
217
299
|
|
|
300
|
+
macro_rules! dispatch_compound {
|
|
301
|
+
($node:expr, $as_method:ident, $var_kind:ident, operator) => {
|
|
302
|
+
if let Some(n) = $node.$as_method() {
|
|
303
|
+
return Some(NeedsChildKind::CompoundWrite {
|
|
304
|
+
var_kind: CompoundVarKind::$var_kind(bytes_to_name(n.name().as_slice())),
|
|
305
|
+
op: CompoundOp::Operator(bytes_to_name(n.binary_operator().as_slice())),
|
|
306
|
+
value: n.value(),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
($node:expr, $as_method:ident, $var_kind:ident, $op:ident) => {
|
|
311
|
+
if let Some(n) = $node.$as_method() {
|
|
312
|
+
return Some(NeedsChildKind::CompoundWrite {
|
|
313
|
+
var_kind: CompoundVarKind::$var_kind(bytes_to_name(n.name().as_slice())),
|
|
314
|
+
op: CompoundOp::$op,
|
|
315
|
+
value: n.value(),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
macro_rules! dispatch_compound_all {
|
|
322
|
+
($node:expr, $op_method:ident, $or_method:ident, $and_method:ident, $var_kind:ident) => {
|
|
323
|
+
dispatch_compound!($node, $op_method, $var_kind, operator);
|
|
324
|
+
dispatch_compound!($node, $or_method, $var_kind, Logical);
|
|
325
|
+
dispatch_compound!($node, $and_method, $var_kind, Logical);
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
dispatch_compound_all!(node,
|
|
330
|
+
as_local_variable_operator_write_node, as_local_variable_or_write_node, as_local_variable_and_write_node, Local);
|
|
331
|
+
dispatch_compound_all!(node,
|
|
332
|
+
as_instance_variable_operator_write_node, as_instance_variable_or_write_node, as_instance_variable_and_write_node, Ivar);
|
|
333
|
+
dispatch_compound_all!(node,
|
|
334
|
+
as_class_variable_operator_write_node, as_class_variable_or_write_node, as_class_variable_and_write_node, ClassVar);
|
|
335
|
+
dispatch_compound_all!(node,
|
|
336
|
+
as_global_variable_operator_write_node, as_global_variable_or_write_node, as_global_variable_and_write_node, GlobalVar);
|
|
337
|
+
dispatch_compound_all!(node,
|
|
338
|
+
as_constant_operator_write_node, as_constant_or_write_node, as_constant_and_write_node, Constant);
|
|
339
|
+
|
|
218
340
|
// Method call: x.upcase, x.each { |i| ... }, or name (implicit self)
|
|
219
341
|
if let Some(call_node) = node.as_call_node() {
|
|
220
342
|
let method_name = bytes_to_name(call_node.name().as_slice());
|
|
@@ -225,6 +347,16 @@ pub(crate) fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<
|
|
|
225
347
|
.unwrap_or_default();
|
|
226
348
|
|
|
227
349
|
if let Some(receiver) = call_node.receiver() {
|
|
350
|
+
if method_name == "new" {
|
|
351
|
+
if let Some(const_read) = receiver.as_constant_read_node() {
|
|
352
|
+
if const_read.name().as_slice() == b"Proc" {
|
|
353
|
+
if let Some(block_node) = block {
|
|
354
|
+
return Some(NeedsChildKind::ProcLiteral { block: block_node });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
228
360
|
// Explicit receiver: x.upcase, x.each { |i| ... }
|
|
229
361
|
let prism_location = call_node
|
|
230
362
|
.call_operator_loc()
|
|
@@ -243,6 +375,12 @@ pub(crate) fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<
|
|
|
243
375
|
} else {
|
|
244
376
|
// No receiver: implicit self method call (e.g., `name`, `puts "hello"`)
|
|
245
377
|
|
|
378
|
+
if matches!(method_name.as_str(), "lambda" | "proc") {
|
|
379
|
+
if let Some(block_node) = block {
|
|
380
|
+
return Some(NeedsChildKind::ProcLiteral { block: block_node });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
246
384
|
if let Some(kind) = match method_name.as_str() {
|
|
247
385
|
"attr_reader" => Some(AttrKind::Reader),
|
|
248
386
|
"attr_writer" => Some(AttrKind::Writer),
|
|
@@ -303,11 +441,36 @@ pub(crate) fn process_needs_child(
|
|
|
303
441
|
match kind {
|
|
304
442
|
NeedsChildKind::IvarWrite { ivar_name, value } => {
|
|
305
443
|
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
306
|
-
Some(
|
|
444
|
+
Some(install_ivar_write(genv, ivar_name, value_vtx))
|
|
445
|
+
}
|
|
446
|
+
NeedsChildKind::ClassVarWrite { cvar_name, value } => {
|
|
447
|
+
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
448
|
+
Some(install_class_var_write(genv, cvar_name, value_vtx))
|
|
449
|
+
}
|
|
450
|
+
NeedsChildKind::GlobalVarWrite { gvar_name, value } => {
|
|
451
|
+
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
452
|
+
Some(install_global_var_write(genv, gvar_name, value_vtx))
|
|
453
|
+
}
|
|
454
|
+
NeedsChildKind::ConstantWrite { const_name, value } => {
|
|
455
|
+
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
456
|
+
Some(install_constant_write(genv, const_name, value_vtx))
|
|
307
457
|
}
|
|
308
458
|
NeedsChildKind::LocalVarWrite { var_name, value } => {
|
|
309
459
|
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
310
|
-
Some(
|
|
460
|
+
Some(install_local_var_write(genv, lenv, changes, var_name, value_vtx))
|
|
461
|
+
}
|
|
462
|
+
NeedsChildKind::CompoundWrite { var_kind, op, value } => {
|
|
463
|
+
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
464
|
+
Some(super::compound_assignments::process_compound_write(
|
|
465
|
+
genv, lenv, changes, var_kind, op, value_vtx,
|
|
466
|
+
))
|
|
467
|
+
}
|
|
468
|
+
NeedsChildKind::ProcLiteral { block } => {
|
|
469
|
+
if let Some(block_node) = block.as_block_node() {
|
|
470
|
+
super::lambdas::process_block_as_proc(genv, lenv, changes, source, &block_node)
|
|
471
|
+
} else {
|
|
472
|
+
None
|
|
473
|
+
}
|
|
311
474
|
}
|
|
312
475
|
NeedsChildKind::MethodCall {
|
|
313
476
|
receiver,
|
|
@@ -361,22 +524,6 @@ pub(crate) fn process_needs_child(
|
|
|
361
524
|
}
|
|
362
525
|
}
|
|
363
526
|
|
|
364
|
-
/// Finish instance variable write after child is processed
|
|
365
|
-
fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
|
|
366
|
-
install_ivar_write(genv, ivar_name, value_vtx)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/// Finish local variable write after child is processed
|
|
370
|
-
fn finish_local_var_write(
|
|
371
|
-
genv: &mut GlobalEnv,
|
|
372
|
-
lenv: &mut LocalEnv,
|
|
373
|
-
changes: &mut ChangeSet,
|
|
374
|
-
var_name: String,
|
|
375
|
-
value_vtx: VertexId,
|
|
376
|
-
) -> VertexId {
|
|
377
|
-
install_local_var_write(genv, lenv, changes, var_name, value_vtx)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
527
|
/// Bundled parameters for method call processing
|
|
381
528
|
struct MethodCallContext<'a> {
|
|
382
529
|
recv_vtx: VertexId,
|
|
@@ -453,3 +600,68 @@ fn finish_method_call(
|
|
|
453
600
|
) -> VertexId {
|
|
454
601
|
install_method_call(genv, recv_vtx, method_name, arg_vtxs, kwarg_vtxs, Some(location), safe_navigation)
|
|
455
602
|
}
|
|
603
|
+
|
|
604
|
+
#[cfg(test)]
|
|
605
|
+
mod tests {
|
|
606
|
+
use crate::analyzer::AstInstaller;
|
|
607
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
608
|
+
use crate::parser::ParseSession;
|
|
609
|
+
use crate::types::Type;
|
|
610
|
+
|
|
611
|
+
fn parse_and_install(source: &str) -> GlobalEnv {
|
|
612
|
+
parse_and_install_with(source, |_| {})
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
fn parse_and_install_with_builtin(source: &str) -> GlobalEnv {
|
|
616
|
+
parse_and_install_with(source, |genv| {
|
|
617
|
+
genv.register_builtin_method(Type::string(), "upcase", Type::string());
|
|
618
|
+
})
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
fn parse_and_install_with(source: &str, setup: impl FnOnce(&mut GlobalEnv)) -> GlobalEnv {
|
|
622
|
+
let session = ParseSession::new();
|
|
623
|
+
let result = session.parse_source(source, "<test>").unwrap();
|
|
624
|
+
let mut genv = GlobalEnv::new();
|
|
625
|
+
setup(&mut genv);
|
|
626
|
+
let mut lenv = LocalEnv::new();
|
|
627
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
628
|
+
|
|
629
|
+
let root = result.node();
|
|
630
|
+
if let Some(program_node) = root.as_program_node() {
|
|
631
|
+
let statements = program_node.statements();
|
|
632
|
+
for stmt in &statements.body() {
|
|
633
|
+
installer.install_node(&stmt);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
installer.finish();
|
|
637
|
+
genv
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
#[test]
|
|
641
|
+
fn test_defined_no_error() {
|
|
642
|
+
let genv = parse_and_install("result = defined?(foo)");
|
|
643
|
+
assert!(genv.type_errors.is_empty());
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
#[test]
|
|
647
|
+
fn test_defined_result_is_string_or_nil() {
|
|
648
|
+
// Calling upcase on String | nil produces an error for the nil branch
|
|
649
|
+
let genv = parse_and_install_with_builtin(
|
|
650
|
+
"result = defined?(foo)\nresult.upcase",
|
|
651
|
+
);
|
|
652
|
+
assert!(
|
|
653
|
+
!genv.type_errors.is_empty(),
|
|
654
|
+
"defined? returns String | nil, so upcase should error on nil branch"
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
#[test]
|
|
659
|
+
fn test_defined_child_not_evaluated() {
|
|
660
|
+
// If child expression were evaluated, 42.upcase would produce a type error
|
|
661
|
+
let genv = parse_and_install_with_builtin("defined?(42.upcase)");
|
|
662
|
+
assert!(
|
|
663
|
+
genv.type_errors.is_empty(),
|
|
664
|
+
"Child expression of defined? should not be evaluated"
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
@@ -10,9 +10,10 @@ use ruby_prism::Node;
|
|
|
10
10
|
|
|
11
11
|
use super::assignments::process_multi_write_node;
|
|
12
12
|
use super::blocks::process_block_node;
|
|
13
|
-
use super::conditionals::{process_case_node, process_if_node, process_unless_node};
|
|
13
|
+
use super::conditionals::{process_case_match_node, process_case_node, process_if_node, process_unless_node};
|
|
14
14
|
use super::definitions::{process_class_node, process_def_node, process_module_node};
|
|
15
15
|
use super::exceptions::{process_begin_node, process_rescue_modifier_node};
|
|
16
|
+
use super::lambdas::process_lambda_node;
|
|
16
17
|
use super::dispatch::{dispatch_needs_child, dispatch_simple, process_needs_child, DispatchResult};
|
|
17
18
|
use super::literals::install_literal_node;
|
|
18
19
|
use super::loops::{process_for_node, process_until_node, process_while_node};
|
|
@@ -20,6 +21,7 @@ use super::operators::{process_and_node, process_or_node};
|
|
|
20
21
|
use super::parentheses::process_parentheses_node;
|
|
21
22
|
use super::returns::process_return_node;
|
|
22
23
|
use super::super_calls;
|
|
24
|
+
use super::yields::process_yield_node;
|
|
23
25
|
|
|
24
26
|
/// Build graph from AST (public API wrapper)
|
|
25
27
|
pub struct AstInstaller<'a> {
|
|
@@ -73,6 +75,10 @@ pub(crate) fn install_node(
|
|
|
73
75
|
return process_block_node(genv, lenv, changes, source, &block_node);
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
if let Some(lambda_node) = node.as_lambda_node() {
|
|
79
|
+
return process_lambda_node(genv, lenv, changes, source, &lambda_node);
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
if let Some(if_node) = node.as_if_node() {
|
|
77
83
|
return process_if_node(genv, lenv, changes, source, &if_node);
|
|
78
84
|
}
|
|
@@ -82,6 +88,9 @@ pub(crate) fn install_node(
|
|
|
82
88
|
if let Some(case_node) = node.as_case_node() {
|
|
83
89
|
return process_case_node(genv, lenv, changes, source, &case_node);
|
|
84
90
|
}
|
|
91
|
+
if let Some(case_match_node) = node.as_case_match_node() {
|
|
92
|
+
return process_case_match_node(genv, lenv, changes, source, &case_match_node);
|
|
93
|
+
}
|
|
85
94
|
|
|
86
95
|
if let Some(begin_node) = node.as_begin_node() {
|
|
87
96
|
return process_begin_node(genv, lenv, changes, source, &begin_node);
|
|
@@ -119,6 +128,32 @@ pub(crate) fn install_node(
|
|
|
119
128
|
return process_return_node(genv, lenv, changes, source, &return_node);
|
|
120
129
|
}
|
|
121
130
|
|
|
131
|
+
if let Some(yield_node) = node.as_yield_node() {
|
|
132
|
+
return process_yield_node(genv, lenv, changes, source, &yield_node);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if let Some(next_node) = node.as_next_node() {
|
|
136
|
+
if let Some(args) = next_node.arguments() {
|
|
137
|
+
for arg in args.arguments().iter() {
|
|
138
|
+
install_node(genv, lenv, changes, source, &arg);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return None;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if let Some(break_node) = node.as_break_node() {
|
|
145
|
+
if let Some(args) = break_node.arguments() {
|
|
146
|
+
for arg in args.arguments().iter() {
|
|
147
|
+
install_node(genv, lenv, changes, source, &arg);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return None;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if node.as_redo_node().is_some() || node.as_retry_node().is_some() {
|
|
154
|
+
return None;
|
|
155
|
+
}
|
|
156
|
+
|
|
122
157
|
if let Some(and_node) = node.as_and_node() {
|
|
123
158
|
return process_and_node(genv, lenv, changes, source, &and_node);
|
|
124
159
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
//! Lambdas/Procs - type inference for lambda and proc literals
|
|
2
|
+
//!
|
|
3
|
+
//! Handles `-> { }`, `lambda { }`, `proc { }`, and `Proc.new { }`.
|
|
4
|
+
|
|
5
|
+
use ruby_prism::{LambdaNode, Node};
|
|
6
|
+
|
|
7
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
8
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
9
|
+
use crate::types::Type;
|
|
10
|
+
|
|
11
|
+
pub(crate) fn process_lambda_node(
|
|
12
|
+
genv: &mut GlobalEnv,
|
|
13
|
+
lenv: &mut LocalEnv,
|
|
14
|
+
changes: &mut ChangeSet,
|
|
15
|
+
source: &str,
|
|
16
|
+
lambda_node: &LambdaNode,
|
|
17
|
+
) -> Option<VertexId> {
|
|
18
|
+
process_proc_body(genv, lenv, changes, source, lambda_node.parameters(), lambda_node.body())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub(crate) fn process_block_as_proc(
|
|
22
|
+
genv: &mut GlobalEnv,
|
|
23
|
+
lenv: &mut LocalEnv,
|
|
24
|
+
changes: &mut ChangeSet,
|
|
25
|
+
source: &str,
|
|
26
|
+
block_node: &ruby_prism::BlockNode,
|
|
27
|
+
) -> Option<VertexId> {
|
|
28
|
+
process_proc_body(genv, lenv, changes, source, block_node.parameters(), block_node.body())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn process_proc_body<'a>(
|
|
32
|
+
genv: &mut GlobalEnv,
|
|
33
|
+
lenv: &mut LocalEnv,
|
|
34
|
+
changes: &mut ChangeSet,
|
|
35
|
+
source: &str,
|
|
36
|
+
parameters: Option<Node<'a>>,
|
|
37
|
+
body: Option<Node<'a>>,
|
|
38
|
+
) -> Option<VertexId> {
|
|
39
|
+
let (_scope_id, merge_vtx) = genv.enter_lambda();
|
|
40
|
+
|
|
41
|
+
let param_vtxs = parameters
|
|
42
|
+
.and_then(|p| p.as_block_parameters_node())
|
|
43
|
+
.map(|bp| {
|
|
44
|
+
super::blocks::install_block_parameters_with_vtxs(genv, lenv, changes, source, &bp)
|
|
45
|
+
})
|
|
46
|
+
.unwrap_or_default();
|
|
47
|
+
|
|
48
|
+
let body_vtx = body.and_then(|b| {
|
|
49
|
+
if let Some(stmts) = b.as_statements_node() {
|
|
50
|
+
super::install::install_statements(genv, lenv, changes, source, &stmts)
|
|
51
|
+
} else {
|
|
52
|
+
super::install::install_node(genv, lenv, changes, source, &b)
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if let Some(vtx) = body_vtx {
|
|
57
|
+
genv.add_edge(vtx, merge_vtx);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
genv.exit_scope();
|
|
61
|
+
|
|
62
|
+
let proc_ty = Type::proc_type_with_vertex(merge_vtx, param_vtxs);
|
|
63
|
+
let proc_vtx = genv.new_source(proc_ty);
|
|
64
|
+
|
|
65
|
+
Some(proc_vtx)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#[cfg(test)]
|
|
69
|
+
mod tests {
|
|
70
|
+
use crate::analyzer::AstInstaller;
|
|
71
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
72
|
+
use crate::parser::ParseSession;
|
|
73
|
+
use crate::types::Type;
|
|
74
|
+
|
|
75
|
+
fn parse_and_install(source: &str) -> GlobalEnv {
|
|
76
|
+
parse_and_install_with(source, |_| {})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn parse_and_install_with_builtin(source: &str) -> GlobalEnv {
|
|
80
|
+
parse_and_install_with(source, |genv| {
|
|
81
|
+
genv.register_builtin_method(Type::string(), "upcase", Type::string());
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn parse_and_install_with(source: &str, setup: impl FnOnce(&mut GlobalEnv)) -> GlobalEnv {
|
|
86
|
+
let session = ParseSession::new();
|
|
87
|
+
let result = session.parse_source(source, "<test>").unwrap();
|
|
88
|
+
let mut genv = GlobalEnv::new();
|
|
89
|
+
setup(&mut genv);
|
|
90
|
+
let mut lenv = LocalEnv::new();
|
|
91
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
92
|
+
|
|
93
|
+
let root = result.node();
|
|
94
|
+
if let Some(program_node) = root.as_program_node() {
|
|
95
|
+
let statements = program_node.statements();
|
|
96
|
+
for stmt in &statements.body() {
|
|
97
|
+
installer.install_node(&stmt);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
installer.finish();
|
|
101
|
+
genv
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[test]
|
|
105
|
+
fn test_lambda_basic_no_crash() {
|
|
106
|
+
let genv = parse_and_install("f = -> { 42 }");
|
|
107
|
+
assert!(genv.type_errors.is_empty());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn test_lambda_with_params_no_crash() {
|
|
112
|
+
let genv = parse_and_install("f = -> (x) { x }");
|
|
113
|
+
assert!(genv.type_errors.is_empty());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#[test]
|
|
117
|
+
fn test_lambda_body_type_error_detected() {
|
|
118
|
+
let genv = parse_and_install_with_builtin("f = -> { 42.upcase }");
|
|
119
|
+
assert!(
|
|
120
|
+
!genv.type_errors.is_empty(),
|
|
121
|
+
"Expected type error for 42.upcase inside lambda"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[test]
|
|
126
|
+
fn test_lambda_method_basic() {
|
|
127
|
+
let genv = parse_and_install("f = lambda { 42 }");
|
|
128
|
+
assert!(genv.type_errors.is_empty());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#[test]
|
|
132
|
+
fn test_proc_method_basic() {
|
|
133
|
+
let genv = parse_and_install("f = proc { 42 }");
|
|
134
|
+
assert!(genv.type_errors.is_empty());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[test]
|
|
138
|
+
fn test_proc_new_basic() {
|
|
139
|
+
let genv = parse_and_install("f = Proc.new { 42 }");
|
|
140
|
+
assert!(genv.type_errors.is_empty());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn test_lambda_call_arg_type_propagation() {
|
|
145
|
+
let genv = parse_and_install_with_builtin(
|
|
146
|
+
"f = ->(x) { x.upcase }\nf.call(42)",
|
|
147
|
+
);
|
|
148
|
+
assert!(
|
|
149
|
+
!genv.type_errors.is_empty(),
|
|
150
|
+
"Expected type error for 42.upcase via lambda arg propagation"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -58,6 +58,14 @@ pub(crate) fn install_literal_node(
|
|
|
58
58
|
return Some(genv.new_source(Type::regexp()));
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// InterpolatedXStringNode: `command #{expr}` → String
|
|
62
|
+
if let Some(interp) = node.as_interpolated_x_string_node() {
|
|
63
|
+
for part in &interp.parts() {
|
|
64
|
+
super::install::install_node(genv, lenv, changes, source, &part);
|
|
65
|
+
}
|
|
66
|
+
return Some(genv.new_source(Type::string()));
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
install_simple_literal(genv, node)
|
|
62
70
|
}
|
|
63
71
|
|
|
@@ -87,6 +95,9 @@ fn install_simple_literal(genv: &mut GlobalEnv, node: &Node) -> Option<VertexId>
|
|
|
87
95
|
if node.as_regular_expression_node().is_some() {
|
|
88
96
|
return Some(genv.new_source(Type::regexp()));
|
|
89
97
|
}
|
|
98
|
+
if node.as_x_string_node().is_some() {
|
|
99
|
+
return Some(genv.new_source(Type::string()));
|
|
100
|
+
}
|
|
90
101
|
None
|
|
91
102
|
}
|
|
92
103
|
|
data/core/src/analyzer/mod.rs
CHANGED
|
@@ -2,11 +2,13 @@ mod assignments;
|
|
|
2
2
|
mod attributes;
|
|
3
3
|
mod blocks;
|
|
4
4
|
mod calls;
|
|
5
|
+
mod compound_assignments;
|
|
5
6
|
mod conditionals;
|
|
6
7
|
mod definitions;
|
|
7
8
|
mod exceptions;
|
|
8
9
|
mod dispatch;
|
|
9
10
|
mod install;
|
|
11
|
+
mod lambdas;
|
|
10
12
|
mod literals;
|
|
11
13
|
mod loops;
|
|
12
14
|
mod operators;
|
|
@@ -15,6 +17,7 @@ mod parentheses;
|
|
|
15
17
|
mod returns;
|
|
16
18
|
mod super_calls;
|
|
17
19
|
mod variables;
|
|
20
|
+
mod yields;
|
|
18
21
|
|
|
19
22
|
pub use install::AstInstaller;
|
|
20
23
|
|