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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 228f256ea9848bd9b2447e097aee95f59f47db2bd252611a7dfa30363be34a23
4
- data.tar.gz: 0ff2217ee68021004331bd580f26739ce067f223ea113b741ab0636f2771e30e
3
+ metadata.gz: 23dc6a87888386dca19ac6c02c051df5712943cffd755ed29437117273347f51
4
+ data.tar.gz: ba211cf132132316f6efa5fbfa5061df93ba9b81b0766192e85a25273720efd8
5
5
  SHA512:
6
- metadata.gz: 3c274c68e498eed4f432b60c4a68a6ca469b7850db539954f584d89bd96cbf3612e03a9a9f43a1b8a8941be6302142e7f34e886b279cde41bc22a0376ba48ac7
7
- data.tar.gz: 57e66add8decf8c951f890f140f76fca86b642af91a7df9d7622c5144414b20f158444838aeca5082a46efa185280747a32117980654ae5f03d0c1dc23bbcd35
6
+ metadata.gz: f485df1d4c80d86bdaa60e4ffb968fc919669e7df4142a91093a2b4004ce758112aeb701936f1e96b2a2a933724c7a38eb9cae982ae55994b664ac604f2c19e8
7
+ data.tar.gz: ad0ab16ba07f4966ff06acde966527bfcac0a96e1a6b65563f5a82dc1454e541ed3c74a35fc8c950a15f662abe2ac00fb66809296c2b4761a9cc523ba3db71de
data/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2026-04-05
9
+
10
+ ### Added
11
+
12
+ - Add pattern matching type inference ([#103](https://github.com/dak2/method-ray/pull/103))
13
+ - Add Lambda/Proc type inference support ([#104](https://github.com/dak2/method-ray/pull/104))
14
+ - Add compound assignment type inference ([#105](https://github.com/dak2/method-ray/pull/105))
15
+ - Add `defined?` type inference support ([#106](https://github.com/dak2/method-ray/pull/106))
16
+ - Add `yield` type inference support ([#107](https://github.com/dak2/method-ray/pull/107))
17
+ - Add control flow keyword support (`break`, `next`, `retry`, `redo`) ([#108](https://github.com/dak2/method-ray/pull/108))
18
+ - Add backtick string type inference ([#109](https://github.com/dak2/method-ray/pull/109))
19
+ - Add predicate type propagation to pattern variables in `case...in` ([#110](https://github.com/dak2/method-ray/pull/110))
20
+ - Add constant type inference support ([#102](https://github.com/dak2/method-ray/pull/102))
21
+ - Add global variable (`$var`) type tracking ([#100](https://github.com/dak2/method-ray/pull/100))
22
+ - Add class variable (`@@var`) type tracking ([#99](https://github.com/dak2/method-ray/pull/99))
23
+
24
+ ### Fixed
25
+
26
+ - Fix uninitialized `@@var` reads silently skipping downstream type checking ([#101](https://github.com/dak2/method-ray/pull/101))
27
+ - Fix release workflow not triggered after tag push ([#88](https://github.com/dak2/method-ray/pull/88))
28
+
8
29
  ## [0.1.10] - 2026-03-24
9
30
 
10
31
  ### Added
@@ -159,6 +180,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
159
180
  - Initial release
160
181
  - `methodray check` - Static type checking for Ruby files
161
182
 
183
+ [0.2.0]: https://github.com/dak2/method-ray/releases/tag/v0.2.0
162
184
  [0.1.10]: https://github.com/dak2/method-ray/releases/tag/v0.1.10
163
185
  [0.1.9]: https://github.com/dak2/method-ray/releases/tag/v0.1.9
164
186
  [0.1.8]: https://github.com/dak2/method-ray/releases/tag/v0.1.8
data/core/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "methodray-core"
3
- version = "0.1.10"
3
+ version = "0.2.0"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -56,7 +56,7 @@ pub(crate) fn process_block_node_with_params(
56
56
  }
57
57
 
58
58
  /// Install block parameters and return their vertex IDs
59
- fn install_block_parameters_with_vtxs(
59
+ pub(crate) fn install_block_parameters_with_vtxs(
60
60
  genv: &mut GlobalEnv,
61
61
  lenv: &mut LocalEnv,
62
62
  changes: &mut ChangeSet,
@@ -0,0 +1,194 @@
1
+ //! Compound assignment handlers (`x += 1`, `x ||= val`, `x &&= val`).
2
+ //!
3
+ //! `||=` and `&&=` use the same Union(existing, val) approximation — a sound
4
+ //! over-approximation without condition narrowing.
5
+
6
+ use crate::env::{GlobalEnv, LocalEnv};
7
+ use crate::graph::{ChangeSet, VertexId};
8
+ use crate::types::Type;
9
+
10
+ use super::calls::install_method_call;
11
+ use super::variables::{
12
+ install_class_var_read, install_class_var_write, install_constant_read, install_constant_write,
13
+ install_global_var_read, install_global_var_write, install_ivar_read, install_ivar_write,
14
+ install_local_var_read, install_local_var_write,
15
+ };
16
+
17
+ pub(crate) enum CompoundVarKind {
18
+ Local(String),
19
+ Ivar(String),
20
+ ClassVar(String),
21
+ GlobalVar(String),
22
+ Constant(String),
23
+ }
24
+
25
+ impl CompoundVarKind {
26
+ fn read(&self, genv: &GlobalEnv, lenv: &LocalEnv) -> Option<VertexId> {
27
+ match self {
28
+ Self::Local(name) => install_local_var_read(lenv, name),
29
+ Self::Ivar(name) => install_ivar_read(genv, name),
30
+ Self::ClassVar(name) => install_class_var_read(genv, name),
31
+ Self::GlobalVar(name) => install_global_var_read(genv, name),
32
+ Self::Constant(name) => install_constant_read(genv, name),
33
+ }
34
+ }
35
+
36
+ fn write(
37
+ self,
38
+ genv: &mut GlobalEnv,
39
+ lenv: &mut LocalEnv,
40
+ changes: &mut ChangeSet,
41
+ value_vtx: VertexId,
42
+ ) -> VertexId {
43
+ match self {
44
+ Self::Local(name) => install_local_var_write(genv, lenv, changes, name, value_vtx),
45
+ Self::Ivar(name) => install_ivar_write(genv, name, value_vtx),
46
+ Self::ClassVar(name) => install_class_var_write(genv, name, value_vtx),
47
+ Self::GlobalVar(name) => install_global_var_write(genv, name, value_vtx),
48
+ Self::Constant(name) => install_constant_write(genv, name, value_vtx),
49
+ }
50
+ }
51
+ }
52
+
53
+ pub(crate) enum CompoundOp {
54
+ Operator(String),
55
+ Logical,
56
+ }
57
+
58
+ pub(crate) fn process_compound_write(
59
+ genv: &mut GlobalEnv,
60
+ lenv: &mut LocalEnv,
61
+ changes: &mut ChangeSet,
62
+ var_kind: CompoundVarKind,
63
+ op: CompoundOp,
64
+ value_vtx: VertexId,
65
+ ) -> VertexId {
66
+ match op {
67
+ CompoundOp::Operator(operator) => {
68
+ let current_vtx = var_kind.read(genv, lenv)
69
+ .unwrap_or_else(|| genv.new_source(Type::Nil));
70
+ let result_vtx = install_method_call(
71
+ genv, current_vtx, operator, vec![value_vtx], None, None, false,
72
+ );
73
+ var_kind.write(genv, lenv, changes, result_vtx)
74
+ }
75
+ CompoundOp::Logical => {
76
+ let merge_vtx = genv.new_vertex();
77
+ if let Some(current_vtx) = var_kind.read(genv, lenv) {
78
+ genv.add_edge(current_vtx, merge_vtx);
79
+ }
80
+ genv.add_edge(value_vtx, merge_vtx);
81
+ var_kind.write(genv, lenv, changes, merge_vtx)
82
+ }
83
+ }
84
+ }
85
+
86
+ #[cfg(test)]
87
+ mod tests {
88
+ use super::*;
89
+
90
+ #[test]
91
+ fn test_operator_write() {
92
+ let mut genv = GlobalEnv::new();
93
+ let mut lenv = LocalEnv::new();
94
+ let mut changes = ChangeSet::new();
95
+
96
+ let int_vtx = genv.new_source(Type::integer());
97
+ install_local_var_write(&mut genv, &mut lenv, &mut changes, "x".to_string(), int_vtx);
98
+
99
+ let rhs_vtx = genv.new_source(Type::integer());
100
+ let result = process_compound_write(
101
+ &mut genv, &mut lenv, &mut changes,
102
+ CompoundVarKind::Local("x".to_string()),
103
+ CompoundOp::Operator("+".to_string()),
104
+ rhs_vtx,
105
+ );
106
+
107
+ assert_ne!(result, int_vtx);
108
+ assert_eq!(install_local_var_read(&lenv, "x"), Some(result));
109
+ }
110
+
111
+ #[test]
112
+ fn test_operator_write_uninitialized() {
113
+ let mut genv = GlobalEnv::new();
114
+ let mut lenv = LocalEnv::new();
115
+ let mut changes = ChangeSet::new();
116
+
117
+ let rhs_vtx = genv.new_source(Type::integer());
118
+ let result = process_compound_write(
119
+ &mut genv, &mut lenv, &mut changes,
120
+ CompoundVarKind::Local("x".to_string()),
121
+ CompoundOp::Operator("+".to_string()),
122
+ rhs_vtx,
123
+ );
124
+
125
+ assert_eq!(install_local_var_read(&lenv, "x"), Some(result));
126
+ }
127
+
128
+ #[test]
129
+ fn test_logical_write_produces_union() {
130
+ let mut genv = GlobalEnv::new();
131
+ let mut lenv = LocalEnv::new();
132
+ let mut changes = ChangeSet::new();
133
+
134
+ let str_vtx = genv.new_source(Type::string());
135
+ install_local_var_write(&mut genv, &mut lenv, &mut changes, "x".to_string(), str_vtx);
136
+
137
+ let int_vtx = genv.new_source(Type::integer());
138
+ let result = process_compound_write(
139
+ &mut genv, &mut lenv, &mut changes,
140
+ CompoundVarKind::Local("x".to_string()),
141
+ CompoundOp::Logical,
142
+ int_vtx,
143
+ );
144
+
145
+ assert_eq!(install_local_var_read(&lenv, "x"), Some(result));
146
+ genv.apply_changes(changes);
147
+ genv.run_all();
148
+ let types = genv.get_receiver_types(result).unwrap();
149
+ assert!(types.contains(&Type::string()));
150
+ assert!(types.contains(&Type::integer()));
151
+ }
152
+
153
+ #[test]
154
+ fn test_logical_write_uninitialized() {
155
+ let mut genv = GlobalEnv::new();
156
+ let mut lenv = LocalEnv::new();
157
+ let mut changes = ChangeSet::new();
158
+
159
+ let str_vtx = genv.new_source(Type::string());
160
+ let result = process_compound_write(
161
+ &mut genv, &mut lenv, &mut changes,
162
+ CompoundVarKind::Local("x".to_string()),
163
+ CompoundOp::Logical,
164
+ str_vtx,
165
+ );
166
+
167
+ genv.apply_changes(changes);
168
+ genv.run_all();
169
+ let types = genv.get_receiver_types(result).unwrap();
170
+ assert!(types.contains(&Type::string()));
171
+ }
172
+
173
+ #[test]
174
+ fn test_ivar_compound_write() {
175
+ let mut genv = GlobalEnv::new();
176
+ let mut lenv = LocalEnv::new();
177
+ let mut changes = ChangeSet::new();
178
+ genv.enter_class("TestClass".to_string(), None);
179
+ genv.enter_method("test_method".to_string());
180
+
181
+ let int_vtx = genv.new_source(Type::integer());
182
+ install_ivar_write(&mut genv, "@count".to_string(), int_vtx);
183
+
184
+ let rhs_vtx = genv.new_source(Type::integer());
185
+ let result = process_compound_write(
186
+ &mut genv, &mut lenv, &mut changes,
187
+ CompoundVarKind::Ivar("@count".to_string()),
188
+ CompoundOp::Operator("+".to_string()),
189
+ rhs_vtx,
190
+ );
191
+
192
+ assert_eq!(result, int_vtx);
193
+ }
194
+ }
@@ -1,4 +1,4 @@
1
- //! Conditionals - if/unless/case type inference
1
+ //! Conditionals - if/unless/case type inference
2
2
  //!
3
3
  //! Collects types from each branch and merges them into a Union
4
4
  //! via edges into a single result Vertex.
@@ -6,9 +6,14 @@
6
6
  use crate::env::{GlobalEnv, LocalEnv};
7
7
  use crate::graph::{ChangeSet, VertexId};
8
8
  use crate::types::Type;
9
- use ruby_prism::{CaseNode, ElseNode, IfNode, Node, UnlessNode, WhenNode};
9
+ use ruby_prism::{
10
+ ArrayPatternNode, CapturePatternNode, CaseMatchNode, CaseNode, ElseNode, FindPatternNode,
11
+ HashPatternNode, IfNode, InNode, Node, UnlessNode, WhenNode,
12
+ };
10
13
 
14
+ use super::bytes_to_name;
11
15
  use super::install::{install_node, install_statements};
16
+ use super::variables::install_local_var_write;
12
17
 
13
18
  /// Process IfNode: if/elsif/else chain
14
19
  pub(crate) fn process_if_node(
@@ -137,6 +142,47 @@ pub(crate) fn process_case_node(
137
142
  Some(result_vtx)
138
143
  }
139
144
 
145
+ /// Process CaseMatchNode: case/in pattern matching
146
+ pub(crate) fn process_case_match_node(
147
+ genv: &mut GlobalEnv,
148
+ lenv: &mut LocalEnv,
149
+ changes: &mut ChangeSet,
150
+ source: &str,
151
+ node: &CaseMatchNode,
152
+ ) -> Option<VertexId> {
153
+ let predicate_vtx = node
154
+ .predicate()
155
+ .and_then(|pred| install_node(genv, lenv, changes, source, &pred));
156
+
157
+ let result_vtx = genv.new_vertex();
158
+
159
+ for condition in &node.conditions() {
160
+ if let Some(in_node) = condition.as_in_node() {
161
+ let vtx = process_in_clause(genv, lenv, changes, source, &in_node, predicate_vtx);
162
+ if let Some(vtx) = vtx {
163
+ genv.add_edge(vtx, result_vtx);
164
+ }
165
+ }
166
+ }
167
+
168
+ let has_else = if let Some(else_node) = node.else_clause() {
169
+ let vtx = process_else_clause(genv, lenv, changes, source, &else_node);
170
+ if let Some(vtx) = vtx {
171
+ genv.add_edge(vtx, result_vtx);
172
+ }
173
+ true
174
+ } else {
175
+ false
176
+ };
177
+
178
+ if !has_else {
179
+ let nil_vtx = genv.new_source(Type::Nil);
180
+ genv.add_edge(nil_vtx, result_vtx);
181
+ }
182
+
183
+ Some(result_vtx)
184
+ }
185
+
140
186
  /// Process subsequent node (elsif chain or else)
141
187
  fn process_subsequent(
142
188
  genv: &mut GlobalEnv,
@@ -188,3 +234,223 @@ fn process_when_clause(
188
234
  .statements()
189
235
  .and_then(|stmts| install_statements(genv, lenv, changes, source, &stmts))
190
236
  }
237
+
238
+ /// Process InNode clause body
239
+ fn process_in_clause(
240
+ genv: &mut GlobalEnv,
241
+ lenv: &mut LocalEnv,
242
+ changes: &mut ChangeSet,
243
+ source: &str,
244
+ in_node: &InNode,
245
+ predicate_vtx: Option<VertexId>,
246
+ ) -> Option<VertexId> {
247
+ process_pattern(genv, lenv, changes, source, &in_node.pattern(), predicate_vtx);
248
+ in_node
249
+ .statements()
250
+ .and_then(|s| install_statements(genv, lenv, changes, source, &s))
251
+ }
252
+
253
+ /// Dispatch pattern processing based on pattern type
254
+ fn process_pattern(
255
+ genv: &mut GlobalEnv,
256
+ lenv: &mut LocalEnv,
257
+ changes: &mut ChangeSet,
258
+ source: &str,
259
+ pattern: &Node,
260
+ predicate_vtx: Option<VertexId>,
261
+ ) {
262
+ // Guard pattern (in x if condition)
263
+ if let Some(if_node) = pattern.as_if_node() {
264
+ if let Some(stmts) = if_node.statements() {
265
+ for stmt in &stmts.body() {
266
+ process_pattern(genv, lenv, changes, source, &stmt, predicate_vtx);
267
+ }
268
+ }
269
+ install_node(genv, lenv, changes, source, &if_node.predicate());
270
+ return;
271
+ }
272
+
273
+ if let Some(cap) = pattern.as_capture_pattern_node() {
274
+ process_capture_pattern(genv, lenv, changes, source, &cap);
275
+ return;
276
+ }
277
+
278
+ // ImplicitNode: hash shorthand pattern { name: } wraps LocalVariableTargetNode
279
+ if let Some(implicit) = pattern.as_implicit_node() {
280
+ process_pattern(genv, lenv, changes, source, &implicit.value(), predicate_vtx);
281
+ return;
282
+ }
283
+
284
+ // LocalVariableTargetNode: single variable binding (in x)
285
+ if let Some(target) = pattern.as_local_variable_target_node() {
286
+ let var_name = bytes_to_name(target.name().as_slice());
287
+ let type_vtx = predicate_vtx.unwrap_or_else(|| genv.new_source(Type::Bot));
288
+ install_local_var_write(genv, lenv, changes, var_name, type_vtx);
289
+ return;
290
+ }
291
+
292
+ if let Some(arr) = pattern.as_array_pattern_node() {
293
+ process_array_pattern(genv, lenv, changes, source, &arr, predicate_vtx);
294
+ return;
295
+ }
296
+
297
+ if let Some(find) = pattern.as_find_pattern_node() {
298
+ process_find_pattern(genv, lenv, changes, source, &find, predicate_vtx);
299
+ return;
300
+ }
301
+
302
+ if let Some(hash) = pattern.as_hash_pattern_node() {
303
+ process_hash_pattern(genv, lenv, changes, source, &hash, predicate_vtx);
304
+ return;
305
+ }
306
+
307
+ // AlternationPatternNode: 1 | 2 | 3
308
+ if let Some(alt) = pattern.as_alternation_pattern_node() {
309
+ process_pattern(genv, lenv, changes, source, &alt.left(), predicate_vtx);
310
+ process_pattern(genv, lenv, changes, source, &alt.right(), predicate_vtx);
311
+ return;
312
+ }
313
+
314
+ // PinnedVariableNode: ^x
315
+ if let Some(pinned) = pattern.as_pinned_variable_node() {
316
+ install_node(genv, lenv, changes, source, &pinned.variable());
317
+ return;
318
+ }
319
+
320
+ // PinnedExpressionNode: ^(expr)
321
+ if let Some(pinned) = pattern.as_pinned_expression_node() {
322
+ install_node(genv, lenv, changes, source, &pinned.expression());
323
+ return;
324
+ }
325
+
326
+ // Literal patterns (Integer, String, etc.) - side effects only
327
+ install_node(genv, lenv, changes, source, pattern);
328
+ }
329
+
330
+ /// Process capture pattern: Integer => x, Api::User => u
331
+ fn process_capture_pattern(
332
+ genv: &mut GlobalEnv,
333
+ lenv: &mut LocalEnv,
334
+ changes: &mut ChangeSet,
335
+ source: &str,
336
+ cap: &CapturePatternNode,
337
+ ) {
338
+ let value_node = cap.value();
339
+ let target = cap.target();
340
+ let var_name = bytes_to_name(target.name().as_slice());
341
+
342
+ let type_vtx = if let Some(name) = super::definitions::extract_constant_path(&value_node) {
343
+ genv.new_source(Type::instance(&name))
344
+ } else {
345
+ install_node(genv, lenv, changes, source, &value_node)
346
+ .unwrap_or_else(|| genv.new_vertex())
347
+ };
348
+
349
+ install_local_var_write(genv, lenv, changes, var_name, type_vtx);
350
+ }
351
+
352
+ // TODO: Remove clone
353
+ fn type_arg_source(genv: &mut GlobalEnv, vtx: VertexId, index: usize) -> Option<VertexId> {
354
+ let source = genv.get_source(vtx)?;
355
+ let ty = match &source.ty {
356
+ Type::Generic { type_args, .. } => type_args.get(index)?.clone(),
357
+ _ => return None,
358
+ };
359
+ Some(genv.new_source(ty))
360
+ }
361
+
362
+ /// Process array pattern: [x, y] or [x, *rest]
363
+ fn process_array_pattern(
364
+ genv: &mut GlobalEnv,
365
+ lenv: &mut LocalEnv,
366
+ changes: &mut ChangeSet,
367
+ source: &str,
368
+ arr: &ArrayPatternNode,
369
+ predicate_vtx: Option<VertexId>,
370
+ ) {
371
+ let element_vtx = predicate_vtx.and_then(|vtx| type_arg_source(genv, vtx, 0));
372
+
373
+ for elem in &arr.requireds() {
374
+ process_pattern(genv, lenv, changes, source, &elem, element_vtx);
375
+ }
376
+
377
+ if let Some(target) = arr
378
+ .rest()
379
+ .and_then(|r| r.as_splat_node())
380
+ .and_then(|s| s.expression())
381
+ .and_then(|e| e.as_local_variable_target_node())
382
+ {
383
+ let var_name = bytes_to_name(target.name().as_slice());
384
+ let rest_vtx = predicate_vtx.unwrap_or_else(|| genv.new_source(Type::array_of(Type::Bot)));
385
+ install_local_var_write(genv, lenv, changes, var_name, rest_vtx);
386
+ }
387
+
388
+ for elem in &arr.posts() {
389
+ process_pattern(genv, lenv, changes, source, &elem, element_vtx);
390
+ }
391
+ }
392
+
393
+ /// Process hash pattern: { name:, age: }
394
+ fn process_hash_pattern(
395
+ genv: &mut GlobalEnv,
396
+ lenv: &mut LocalEnv,
397
+ changes: &mut ChangeSet,
398
+ source: &str,
399
+ hash: &HashPatternNode,
400
+ predicate_vtx: Option<VertexId>,
401
+ ) {
402
+ let value_vtx = predicate_vtx.and_then(|vtx| type_arg_source(genv, vtx, 1));
403
+
404
+ for elem in &hash.elements() {
405
+ if let Some(assoc) = elem.as_assoc_node() {
406
+ process_pattern(genv, lenv, changes, source, &assoc.value(), value_vtx);
407
+ }
408
+ }
409
+
410
+ if let Some(target) = hash
411
+ .rest()
412
+ .and_then(|r| r.as_assoc_splat_node())
413
+ .and_then(|s| s.value())
414
+ .and_then(|v| v.as_local_variable_target_node())
415
+ {
416
+ let var_name = bytes_to_name(target.name().as_slice());
417
+ let hash_vtx = genv.new_source(Type::hash());
418
+ install_local_var_write(genv, lenv, changes, var_name, hash_vtx);
419
+ }
420
+ }
421
+
422
+ /// Process find pattern: [*, x, *]
423
+ fn process_find_pattern(
424
+ genv: &mut GlobalEnv,
425
+ lenv: &mut LocalEnv,
426
+ changes: &mut ChangeSet,
427
+ source: &str,
428
+ find: &FindPatternNode,
429
+ predicate_vtx: Option<VertexId>,
430
+ ) {
431
+ let element_vtx = predicate_vtx.and_then(|vtx| type_arg_source(genv, vtx, 0));
432
+ let rest_vtx = predicate_vtx.unwrap_or_else(|| genv.new_source(Type::array_of(Type::Bot)));
433
+
434
+ if let Some(target) = find
435
+ .left()
436
+ .expression()
437
+ .and_then(|e| e.as_local_variable_target_node())
438
+ {
439
+ let var_name = bytes_to_name(target.name().as_slice());
440
+ install_local_var_write(genv, lenv, changes, var_name, rest_vtx);
441
+ }
442
+
443
+ for elem in &find.requireds() {
444
+ process_pattern(genv, lenv, changes, source, &elem, element_vtx);
445
+ }
446
+
447
+ if let Some(target) = find
448
+ .right()
449
+ .as_splat_node()
450
+ .and_then(|s| s.expression())
451
+ .and_then(|e| e.as_local_variable_target_node())
452
+ {
453
+ let var_name = bytes_to_name(target.name().as_slice());
454
+ install_local_var_write(genv, lenv, changes, var_name, rest_vtx);
455
+ }
456
+ }