1use crate::functions::{ClosureFunction, FunctionKind};
2use crate::lexer::{LogicalOperator, Operator};
3use crate::parser::{Node, NodeMetadata};
4use crate::variable::Variable;
5use ahash::HashSet;
6use nohash_hasher::BuildNoHashHasher;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::rc::Rc;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(tag = "type", rename_all = "snake_case")]
13pub enum ReadDependency {
14 Direct {
15 path: Vec<Rc<str>>,
16 #[serde(default)]
17 span: (u32, u32),
18 #[serde(default, skip_serializing_if = "is_false")]
19 via_index: bool,
20 },
21 Iteration {
22 collection: Vec<Rc<str>>,
23 #[serde(default)]
24 span: (u32, u32),
25 #[serde(skip_serializing_if = "Option::is_none")]
26 alias: Option<Rc<str>>,
27 reads: Vec<ReadDependency>,
28 },
29 Unresolved {
30 path: Vec<Rc<str>>,
31 #[serde(default)]
32 span: (u32, u32),
33 },
34}
35
36fn is_false(b: &bool) -> bool {
37 !*b
38}
39
40impl ReadDependency {
41 pub fn without_spans(&self) -> Self {
42 match self {
43 ReadDependency::Direct {
44 path, via_index, ..
45 } => ReadDependency::Direct {
46 path: path.clone(),
47 span: (0, 0),
48 via_index: *via_index,
49 },
50 ReadDependency::Iteration {
51 collection,
52 alias,
53 reads,
54 ..
55 } => ReadDependency::Iteration {
56 collection: collection.clone(),
57 span: (0, 0),
58 alias: alias.clone(),
59 reads: reads.iter().map(|r| r.without_spans()).collect(),
60 },
61 ReadDependency::Unresolved { path, .. } => ReadDependency::Unresolved {
62 path: path.clone(),
63 span: (0, 0),
64 },
65 }
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct Reference {
71 pub path: Vec<Rc<str>>,
72 pub spans: Vec<(u32, u32)>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub via_alias: Option<AliasBinding>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub via_index: Option<Vec<Rc<str>>>,
77}
78
79impl Reference {
80 pub fn without_via_alias(&self) -> Self {
81 Self {
82 path: self.path.clone(),
83 spans: self.spans.clone(),
84 via_alias: None,
85 via_index: None,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
91pub struct AliasBinding {
92 pub alias: Rc<str>,
93 pub collection: Vec<Rc<str>>,
94}
95
96#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
97pub struct DependencyResult {
98 pub reads: Vec<ReadDependency>,
99 pub references: Vec<Reference>,
100}
101
102type MetadataMap = HashMap<usize, NodeMetadata, BuildNoHashHasher<usize>>;
103
104enum ChainSegment<'a> {
105 Field { name: Rc<str>, prop: &'a Node<'a> },
106 Dynamic { prop: &'a Node<'a> },
107}
108
109struct FlatChain<'a> {
110 root: &'a Node<'a>,
111 segments: Vec<ChainSegment<'a>>,
112}
113
114pub(crate) struct DependencyResolutionWalker<'a> {
115 reads: Vec<ReadDependency>,
116 references: Vec<Reference>,
117 metadata: &'a MetadataMap,
118}
119
120#[derive(Debug, Clone, Default)]
121struct Scope {
122 locals: HashSet<Rc<str>>,
123 bindings: Vec<Vec<Rc<str>>>,
124 aliases: HashMap<Rc<str>, Vec<Rc<str>>>,
125 unresolved_aliases: HashSet<Rc<str>>,
126 pointer_collection: Option<Vec<Rc<str>>>,
127}
128
129impl Scope {
130 fn is_local(&self, path: &[Rc<str>]) -> bool {
131 if path.is_empty() {
132 return false;
133 }
134
135 if self.locals.contains(&path[0]) {
136 return true;
137 }
138
139 self.bindings
140 .iter()
141 .any(|b| path.len() >= b.len() && path.iter().zip(b.iter()).all(|(a, b)| a == b))
142 }
143
144 fn alias_binding_for(&self, path: &[Rc<str>]) -> Option<AliasBinding> {
145 let root = path.first()?;
146 let collection = self.aliases.get(root)?;
147
148 Some(AliasBinding {
149 alias: root.clone(),
150 collection: collection.clone(),
151 })
152 }
153
154 fn expand_alias_root(&self, path: &[Rc<str>]) -> Vec<Rc<str>> {
155 match path.first().and_then(|root| self.aliases.get(root)) {
156 Some(prefix) => prefix.iter().chain(path.iter().skip(1)).cloned().collect(),
157 None => path.to_vec(),
158 }
159 }
160}
161
162fn node_address(node: &Node) -> usize {
163 node as *const Node as usize
164}
165
166impl<'a> DependencyResolutionWalker<'a> {
167 pub fn walk(root: &Node, metadata: &'a MetadataMap) -> DependencyResult {
168 Self::walk_with_locals(root, metadata, &[])
169 }
170
171 pub fn walk_with_locals(
172 root: &Node,
173 metadata: &'a MetadataMap,
174 locals: &[&str],
175 ) -> DependencyResult {
176 let mut walker = Self {
177 reads: Vec::new(),
178 references: Vec::new(),
179 metadata,
180 };
181
182 let mut scope = Scope::default();
183 scope.locals.extend(locals.iter().map(|l| Rc::from(*l)));
184 walker.resolve(root, &mut scope);
185
186 DependencyResult {
187 reads: walker.reads,
188 references: walker.references,
189 }
190 }
191
192 pub fn field_dependencies(
193 root: &Node,
194 metadata: &'a MetadataMap,
195 field_path: &[&str],
196 ) -> Option<Vec<ReadDependency>> {
197 let mut bindings: Vec<(Rc<str>, Vec<Rc<str>>)> = Vec::new();
198 let mut values: Vec<&Node> = Vec::new();
199 Self::navigate_field(root, field_path, &mut bindings, &mut values);
200 if values.is_empty() {
201 return None;
202 }
203
204 let mut reads: Vec<ReadDependency> = Vec::new();
205 for value in values {
206 let (r, _refs) = Self::walk_inner(value, &mut Scope::default(), metadata);
207 reads.extend(r);
208 }
209
210 let mut wrapped = reads;
211 for (alias, collection) in bindings.into_iter().rev() {
212 wrapped = vec![ReadDependency::Iteration {
213 collection,
214 span: (0, 0),
215 alias: Some(alias),
216 reads: wrapped,
217 }];
218 }
219 Some(wrapped)
220 }
221
222 fn navigate_field<'n>(
223 node: &'n Node<'n>,
224 field_path: &[&str],
225 bindings: &mut Vec<(Rc<str>, Vec<Rc<str>>)>,
226 out: &mut Vec<&'n Node<'n>>,
227 ) {
228 match node {
229 Node::Parenthesized(inner) => Self::navigate_field(inner, field_path, bindings, out),
230 Node::Assignments {
231 output: Some(output),
232 ..
233 } => Self::navigate_field(output, field_path, bindings, out),
234 Node::Conditional {
235 on_true, on_false, ..
236 } => {
237 Self::navigate_field(on_true, field_path, bindings, out);
238 Self::navigate_field(on_false, field_path, bindings, out);
239 }
240 Node::FunctionCall {
241 kind: FunctionKind::Closure(ClosureFunction::Map | ClosureFunction::FlatMap),
242 arguments,
243 } if arguments.len() >= 2 => {
244 if let Node::Closure { body, alias } = arguments[1] {
245 if let (Some(a), Some(src)) =
246 (alias, Self::collection_source_path(arguments[0]))
247 {
248 bindings.push((Rc::from(*a), src));
249 }
250 Self::navigate_field(body, field_path, bindings, out);
251 }
252 }
253 Node::Object(pairs) => {
254 let Some((first, rest)) = field_path.split_first() else {
255 return;
256 };
257 let value = pairs.iter().find_map(|(k, v)| match k {
258 Node::String(s) if s == first => Some(*v),
259 _ => None,
260 });
261 if let Some(value) = value {
262 if rest.is_empty() {
263 out.push(value);
264 } else {
265 Self::navigate_field(value, rest, bindings, out);
266 }
267 }
268 }
269 _ => {}
270 }
271 }
272
273 fn walk_inner(
274 node: &Node,
275 scope: &mut Scope,
276 metadata: &'a MetadataMap,
277 ) -> (Vec<ReadDependency>, Vec<Reference>) {
278 let mut walker = Self {
279 reads: Vec::new(),
280 references: Vec::new(),
281 metadata,
282 };
283 walker.resolve(node, scope);
284 (walker.reads, walker.references)
285 }
286
287 fn node_span(&self, node: &Node) -> (u32, u32) {
288 node.span()
289 .or_else(|| self.metadata.get(&node_address(node)).map(|m| m.span))
290 .unwrap_or_default()
291 }
292
293 fn extract_path_with_spans(&self, node: &Node) -> Option<(Vec<Rc<str>>, Vec<(u32, u32)>)> {
294 match node {
295 Node::Identifier(name) => Some((vec![Rc::from(*name)], vec![self.node_span(node)])),
296 Node::Root => Some((vec![Variable::root_key_rc()], vec![self.node_span(node)])),
297 Node::Member { node: n, property } => {
298 let (mut path, mut spans) = self.extract_path_with_spans(n)?;
299 match property {
300 Node::String(key) => {
301 path.push(Rc::from(*key));
302 spans.push(self.node_span(property));
303 Some((path, spans))
304 }
305 _ => None,
306 }
307 }
308 _ => None,
309 }
310 }
311
312 fn collection_source_path(node: &Node) -> Option<Vec<Rc<str>>> {
313 match node {
314 Node::Identifier(name) => Some(vec![Rc::from(*name)]),
315 Node::Root => Some(vec![Variable::root_key_rc()]),
316 Node::Member { .. } => Self::extract_read_path(node),
317 Node::Parenthesized(inner) => Self::collection_source_path(inner),
318 Node::Binary {
319 left,
320 operator: Operator::Logical(LogicalOperator::NullishCoalescing),
321 ..
322 } => Self::collection_source_path(left),
323 Node::FunctionCall {
324 kind: FunctionKind::Closure(ClosureFunction::Filter),
325 arguments,
326 } if !arguments.is_empty() => Self::collection_source_path(arguments[0]),
327 _ => None,
328 }
329 }
330
331 fn flatten_member_chain<'n>(node: &'n Node<'n>) -> Option<FlatChain<'n>> {
332 let mut segments: Vec<ChainSegment<'n>> = Vec::new();
333 let mut current = node;
334 loop {
335 match current {
336 Node::Member { node: n, property } => {
337 segments.push(match property {
338 Node::String(key) => ChainSegment::Field {
339 name: Rc::from(*key),
340 prop: property,
341 },
342 _ => ChainSegment::Dynamic { prop: property },
343 });
344 current = n;
345 }
346 Node::Identifier(_) | Node::Root | Node::Pointer => {
347 segments.reverse();
348 return Some(FlatChain {
349 root: current,
350 segments,
351 });
352 }
353 _ => return None,
354 }
355 }
356 }
357
358 fn resolve_member_chain(&mut self, node: &Node, scope: &mut Scope) {
359 let Some(chain) = Self::flatten_member_chain(node) else {
360 if let Node::Member { node: n, property } = node {
361 self.resolve(n, scope);
362 self.resolve(property, scope);
363 }
364
365 return;
366 };
367
368 let root_name: Rc<str> = match chain.root {
369 Node::Identifier(name) if *name == "$" => {
370 if scope.pointer_collection.is_some() {
371 self.resolve_pointer_chain(&chain, scope);
372 } else {
373 self.reference_dollar_chain(&chain, scope);
374 }
375 return;
376 }
377 Node::Identifier(name) => Rc::from(*name),
378 Node::Root => Variable::root_key_rc(),
379 Node::Pointer => {
380 self.resolve_pointer_chain(&chain, scope);
381 return;
382 }
383 _ => return,
384 };
385 let root_span = self.node_span(chain.root);
386
387 if scope.unresolved_aliases.contains(&root_name) {
388 let mut path = vec![root_name.clone()];
389 let mut spans = vec![root_span];
390 for segment in &chain.segments {
391 match segment {
392 ChainSegment::Field { name, prop } => {
393 path.push(name.clone());
394 spans.push(self.node_span(prop));
395 }
396 ChainSegment::Dynamic { prop } => self.resolve(prop, scope),
397 }
398 }
399 let span = match (spans.first(), spans.last()) {
400 (Some(first), Some(last)) => (first.0, last.1),
401 _ => Default::default(),
402 };
403 self.reads.push(ReadDependency::Unresolved {
404 path: path.clone(),
405 span,
406 });
407 self.references.push(Reference {
408 path,
409 spans,
410 via_alias: None,
411 via_index: None,
412 });
413
414 return;
415 }
416
417 let via_alias_first =
418 scope
419 .aliases
420 .get(&root_name)
421 .cloned()
422 .map(|collection| AliasBinding {
423 alias: root_name.clone(),
424 collection,
425 });
426
427 if via_alias_first.is_none() && scope.is_local(std::slice::from_ref(&root_name)) {
428 for segment in &chain.segments {
429 if let ChainSegment::Dynamic { prop } = segment {
430 self.resolve(prop, scope);
431 }
432 }
433 return;
434 }
435
436 let mut group_path: Vec<Rc<str>> = vec![root_name.clone()];
437 let mut group_spans: Vec<(u32, u32)> = vec![root_span];
438 let mut cumulative: Vec<Rc<str>> = vec![root_name];
439 let mut via_index_for_group: Option<Vec<Rc<str>>> = None;
440 let mut is_first_group = true;
441
442 for segment in &chain.segments {
443 match segment {
444 ChainSegment::Field { name, prop } => {
445 group_path.push(name.clone());
446 group_spans.push(self.node_span(prop));
447 cumulative.push(name.clone());
448 }
449 ChainSegment::Dynamic { prop } => {
450 let emittable = is_first_group || !group_path.is_empty();
451 if emittable && !group_path.is_empty() {
452 let via_alias = if is_first_group {
453 via_alias_first.clone()
454 } else {
455 None
456 };
457 self.emit_group(
458 std::mem::take(&mut group_path),
459 std::mem::take(&mut group_spans),
460 via_alias,
461 via_index_for_group.clone(),
462 scope,
463 );
464 }
465 group_path.clear();
466 group_spans.clear();
467 self.resolve(prop, scope);
468 via_index_for_group = Some(cumulative.clone());
469 is_first_group = false;
470 }
471 }
472 }
473
474 if !group_path.is_empty() {
475 let via_alias = if is_first_group {
476 via_alias_first
477 } else {
478 None
479 };
480 self.emit_group(
481 group_path,
482 group_spans,
483 via_alias,
484 via_index_for_group,
485 scope,
486 );
487 }
488 }
489
490 fn reference_dollar_chain(&mut self, chain: &FlatChain, scope: &mut Scope) {
491 let mut path: Vec<Rc<str>> = vec![Variable::dollar_key_rc()];
492 let mut spans = vec![self.node_span(chain.root)];
493 let mut grouping = true;
494 for segment in &chain.segments {
495 match segment {
496 ChainSegment::Field { name, prop } if grouping => {
497 path.push(name.clone());
498 spans.push(self.node_span(prop));
499 }
500 ChainSegment::Field { .. } => {}
501 ChainSegment::Dynamic { prop } => {
502 grouping = false;
503 self.resolve(prop, scope);
504 }
505 }
506 }
507 if path.len() > 1 {
508 self.references.push(Reference {
509 path,
510 spans,
511 via_alias: None,
512 via_index: None,
513 });
514 }
515 }
516
517 fn resolve_pointer_chain(&mut self, chain: &FlatChain, scope: &mut Scope) {
518 let Some(collection) = scope.pointer_collection.clone() else {
519 for segment in &chain.segments {
520 if let ChainSegment::Dynamic { prop } = segment {
521 self.resolve(prop, scope);
522 }
523 }
524 return;
525 };
526
527 let mut group_path: Vec<Rc<str>> = Vec::new();
528 let mut group_spans: Vec<(u32, u32)> = Vec::new();
529 let mut cumulative: Vec<Rc<str>> = collection.clone();
530 let mut via_index_for_group = collection;
531
532 for segment in &chain.segments {
533 match segment {
534 ChainSegment::Field { name, prop } => {
535 group_path.push(name.clone());
536 group_spans.push(self.node_span(prop));
537 cumulative.push(name.clone());
538 }
539 ChainSegment::Dynamic { prop } => {
540 if !group_path.is_empty() {
541 self.references.push(Reference {
542 path: std::mem::take(&mut group_path),
543 spans: std::mem::take(&mut group_spans),
544 via_alias: None,
545 via_index: Some(via_index_for_group.clone()),
546 });
547 }
548 self.resolve(prop, scope);
549 via_index_for_group = cumulative.clone();
550 }
551 }
552 }
553
554 if !group_path.is_empty() {
555 self.references.push(Reference {
556 path: group_path,
557 spans: group_spans,
558 via_alias: None,
559 via_index: Some(via_index_for_group),
560 });
561 }
562 }
563
564 fn emit_group(
565 &mut self,
566 path: Vec<Rc<str>>,
567 spans: Vec<(u32, u32)>,
568 via_alias: Option<AliasBinding>,
569 via_index: Option<Vec<Rc<str>>>,
570 scope: &Scope,
571 ) {
572 if path.is_empty() {
573 return;
574 }
575 if via_alias.is_none() && scope.is_local(&path) {
576 return;
577 }
578 let span = match (spans.first(), spans.last()) {
579 (Some(first), Some(last)) => (first.0, last.1),
580 _ => Default::default(),
581 };
582 let read_path = match &via_index {
583 Some(prefix) => prefix.iter().chain(path.iter()).cloned().collect(),
584 None => path.clone(),
585 };
586 self.reads.push(ReadDependency::Direct {
587 path: read_path,
588 span,
589 via_index: via_index.is_some(),
590 });
591 self.references.push(Reference {
592 path,
593 spans,
594 via_alias,
595 via_index,
596 });
597 }
598
599 #[cfg_attr(not(target_family = "wasm"), recursive::recursive)]
600 fn resolve(&mut self, node: &Node, scope: &mut Scope) {
601 match node {
602 Node::Identifier(name) => {
603 let path = vec![Rc::from(*name)];
604 let span = self.node_span(node);
605 if scope.unresolved_aliases.contains(&path[0]) {
606 self.reads.push(ReadDependency::Unresolved {
607 path: path.clone(),
608 span,
609 });
610 self.references.push(Reference {
611 path,
612 spans: vec![span],
613 via_alias: None,
614 via_index: None,
615 });
616 return;
617 }
618 let via_alias =
619 scope
620 .aliases
621 .get(&path[0])
622 .cloned()
623 .map(|collection| AliasBinding {
624 alias: path[0].clone(),
625 collection,
626 });
627 if via_alias.is_some() || !scope.is_local(&path) {
628 self.reads.push(ReadDependency::Direct {
629 path: path.clone(),
630 span,
631 via_index: false,
632 });
633 self.references.push(Reference {
634 path,
635 spans: vec![span],
636 via_alias,
637 via_index: None,
638 });
639 }
640 }
641
642 Node::Member { .. } => self.resolve_member_chain(node, scope),
643
644 Node::Assignments { list, output } => {
645 for (key, value) in list.iter() {
646 self.resolve(value, scope);
647
648 if let Some(path) = Self::extract_binding_path(key) {
649 if path.len() == 1 {
650 scope.locals.insert(path[0].clone());
651 } else if path.len() > 1 {
652 scope.bindings.push(path);
653 }
654 }
655 }
656
657 if let Some(output) = output {
658 self.resolve(output, scope);
659 }
660 }
661
662 Node::FunctionCall { kind, arguments } => {
663 if let FunctionKind::Closure(_) = kind {
664 if arguments.len() >= 2 {
665 let collection_node = arguments[0];
666 let closure_node = arguments[1];
667
668 let collection_info = self.extract_path_with_spans(collection_node);
669 let collection_source = collection_info
670 .as_ref()
671 .map(|(p, _)| p.clone())
672 .or_else(|| Self::collection_source_path(collection_node));
673
674 let (alias, inner_reads, inner_refs) = match closure_node {
675 Node::Closure { body, alias } => {
676 let mut inner_scope = scope.clone();
677 inner_scope.locals.insert(Variable::dollar_key_rc());
678 inner_scope.pointer_collection = collection_source
679 .as_ref()
680 .filter(|source| !scope.is_local(source))
681 .map(|source| scope.expand_alias_root(source));
682
683 match (alias, collection_source.as_ref()) {
684 (Some(alias_name), Some(source)) => {
685 let expanded = scope.expand_alias_root(source);
686 if scope.is_local(&expanded) {
687 inner_scope
688 .unresolved_aliases
689 .insert(Rc::from(*alias_name));
690 } else {
691 inner_scope
692 .aliases
693 .insert(Rc::from(*alias_name), expanded);
694 }
695 }
696 (Some(alias_name), None) => {
697 inner_scope
698 .unresolved_aliases
699 .insert(Rc::from(*alias_name));
700 }
701 _ => {}
702 }
703 let (reads, refs) =
704 Self::walk_inner(body, &mut inner_scope, self.metadata);
705 (alias.map(|a| Rc::from(a)), reads, refs)
706 }
707 _ => {
708 self.resolve(closure_node, scope);
709 return;
710 }
711 };
712
713 match collection_info {
714 Some((collection, spans)) if !scope.is_local(&collection) => {
715 let span = self.node_span(collection_node);
716 self.references.push(Reference {
717 path: collection.clone(),
718 spans,
719 via_alias: scope.alias_binding_for(&collection),
720 via_index: None,
721 });
722 self.references.extend(inner_refs);
723 self.reads.push(ReadDependency::Iteration {
724 collection,
725 span,
726 alias,
727 reads: inner_reads,
728 });
729 }
730 _ => {
731 self.resolve(collection_node, scope);
732 match collection_source {
733 Some(source)
734 if !source.is_empty() && !scope.is_local(&source) =>
735 {
736 let span = self.node_span(collection_node);
737 self.references.extend(inner_refs);
738 self.reads.push(ReadDependency::Iteration {
739 collection: source,
740 span,
741 alias,
742 reads: inner_reads,
743 });
744 }
745 _ => {
746 self.reads.extend(inner_reads);
747 self.references.extend(inner_refs);
748 }
749 }
750 }
751 }
752 } else {
753 for arg in arguments.iter() {
754 self.resolve(arg, scope);
755 }
756 }
757 } else {
758 for arg in arguments.iter() {
759 self.resolve(arg, scope);
760 }
761 }
762 }
763
764 Node::Closure { body, alias: _ } => {
765 let mut inner = scope.clone();
766 inner.locals.insert(Variable::dollar_key_rc());
767 inner.pointer_collection = None;
768 self.resolve(body, &mut inner);
769 }
770
771 Node::MethodCall {
772 this, arguments, ..
773 } => {
774 self.resolve(this, scope);
775 for arg in arguments.iter() {
776 self.resolve(arg, scope);
777 }
778 }
779
780 Node::Binary { left, right, .. } => {
781 self.resolve(left, scope);
782 self.resolve(right, scope);
783 }
784
785 Node::Unary { node, .. } => self.resolve(node, scope),
786
787 Node::Conditional {
788 condition,
789 on_true,
790 on_false,
791 } => {
792 self.resolve(condition, scope);
793 self.resolve(on_true, scope);
794 self.resolve(on_false, scope);
795 }
796
797 Node::Parenthesized(n) => self.resolve(n, scope),
798
799 Node::Array(items) => {
800 for item in items.iter() {
801 self.resolve(item, scope);
802 }
803 }
804
805 Node::Object(pairs) => {
806 for (k, v) in pairs.iter() {
807 self.resolve(k, scope);
808 self.resolve(v, scope);
809 }
810 }
811
812 Node::TemplateString(parts) => {
813 for part in parts.iter() {
814 self.resolve(part, scope);
815 }
816 }
817
818 Node::Slice { node, from, to } => {
819 self.resolve(node, scope);
820 if let Some(f) = from {
821 self.resolve(f, scope);
822 }
823 if let Some(t) = to {
824 self.resolve(t, scope);
825 }
826 }
827
828 Node::Interval { left, right, .. } => {
829 self.resolve(left, scope);
830 self.resolve(right, scope);
831 }
832
833 Node::Error { node, .. } => {
834 if let Some(n) = node {
835 self.resolve(n, scope);
836 }
837 }
838
839 Node::Root => {
840 let path: Vec<Rc<str>> = vec![Variable::root_key_rc()];
841 let span = self.node_span(node);
842 self.reads.push(ReadDependency::Direct {
843 path: path.clone(),
844 span,
845 via_index: false,
846 });
847 self.references.push(Reference {
848 path,
849 spans: vec![span],
850 via_alias: None,
851 via_index: None,
852 });
853 }
854
855 Node::Null | Node::Bool(_) | Node::Number(_) | Node::String(_) | Node::Pointer => {}
856 }
857 }
858
859 fn extract_binding_path(node: &Node) -> Option<Vec<Rc<str>>> {
860 match node {
861 Node::String(s) => Some(s.split('.').map(Rc::from).collect()),
862 _ => Self::extract_read_path(node),
863 }
864 }
865
866 fn extract_read_path(node: &Node) -> Option<Vec<Rc<str>>> {
867 match node {
868 Node::Identifier(name) => Some(vec![Rc::from(*name)]),
869 Node::Root => Some(vec![Variable::root_key_rc()]),
870 Node::Member { node, property } => {
871 let mut path = Self::extract_read_path(node)?;
872 match property {
873 Node::String(key) => {
874 path.push(Rc::from(*key));
875 Some(path)
876 }
877 _ => None,
878 }
879 }
880 _ => None,
881 }
882 }
883}