1use std::cell::RefCell;
24use std::rc::{Rc, Weak};
25use std::sync::Arc;
26
27use graphrefly_core::{
28 Core, EqualsMode, FnId, HandleId, LockId, NodeId, PauseError, ResumeReport, SetDepsError, Sink,
29 SubscriptionId, TopologySubscriptionId,
30};
31use indexmap::IndexMap;
32
33use crate::debug::DebugBindingBoundary;
34use crate::describe::{
35 describe_of, describe_reactive_in, DescribeSink, GraphDescribeOutput, ReactiveDescribeHandle,
36};
37use crate::mount::{GraphRemoveAudit, MountError};
38use crate::observe::{GraphObserveAll, GraphObserveAllReactive, GraphObserveOne};
39
40pub(crate) const PATH_SEP: &str = "::";
42
43#[derive(Debug, thiserror::Error)]
45pub enum RemoveError {
46 #[error("Graph::remove: name `{0}` not found (neither a node nor a mounted subgraph)")]
47 NotFound(String),
48 #[error("Graph::remove: graph has been destroyed")]
49 Destroyed,
50}
51
52#[derive(Debug, Clone, Copy)]
54pub enum SignalKind {
55 Invalidate,
57 Pause(LockId),
59 Resume(LockId),
61 Complete,
63 Error(HandleId),
65}
66
67#[derive(Debug, thiserror::Error)]
69pub enum PathError {
70 #[error("Path is empty")]
72 Empty,
73 #[error("Path segment `..` used on root graph (no parent)")]
75 NoParent,
76 #[error("Path segment `{0}` does not match any child graph")]
78 ChildNotFound(String),
79 #[error("Graph has been destroyed")]
81 Destroyed,
82}
83
84#[derive(Debug, thiserror::Error)]
86pub enum NameError {
87 #[error("Graph::add: name `{0}` already registered in this graph")]
88 Collision(String),
89 #[error("Graph: name `{0}` may not contain the `::` path separator")]
90 InvalidName(String),
91 #[error("Graph: name `{0}` uses the reserved `_anon_` prefix (collides with anonymous node describe format)")]
92 ReservedPrefix(String),
93 #[error("Graph: graph has been destroyed; further registration refused")]
94 Destroyed,
95}
96
97pub(crate) type NamespaceChangeSink = Arc<dyn Fn(&Core)>;
107
108pub struct GraphInner {
115 pub(crate) name: String,
116 pub(crate) names: IndexMap<String, NodeId>,
119 pub(crate) names_inverse: IndexMap<NodeId, String>,
121 pub(crate) children: IndexMap<String, Rc<RefCell<GraphInner>>>,
125 pub(crate) parent: Option<Weak<RefCell<GraphInner>>>,
128 pub(crate) destroyed: bool,
130 pub(crate) namespace_sinks: IndexMap<u64, NamespaceChangeSink>,
133 pub(crate) next_ns_sink_id: u64,
134}
135
136#[derive(Clone)]
146pub struct Graph {
147 pub(crate) inner: Rc<RefCell<GraphInner>>,
148}
149
150impl std::fmt::Debug for Graph {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 let inner = self.inner.borrow_mut();
153 f.debug_struct("Graph")
154 .field("name", &inner.name)
155 .field("node_count", &inner.names.len())
156 .field("subgraph_count", &inner.children.len())
157 .field("destroyed", &inner.destroyed)
158 .finish_non_exhaustive()
159 }
160}
161
162#[allow(clippy::missing_panics_doc, clippy::must_use_candidate)]
166impl Graph {
167 #[must_use]
172 pub fn new(name: impl Into<String>) -> Self {
173 Self::with_parent(name.into(), None)
174 }
175
176 pub(crate) fn with_parent(name: String, parent: Option<Weak<RefCell<GraphInner>>>) -> Self {
177 Self {
178 inner: Rc::new(RefCell::new(GraphInner {
179 name,
180 names: IndexMap::new(),
181 names_inverse: IndexMap::new(),
182 children: IndexMap::new(),
183 parent,
184 destroyed: false,
185 namespace_sinks: IndexMap::new(),
186 next_ns_sink_id: 0,
187 })),
188 }
189 }
190
191 pub(crate) fn from_inner(inner: Rc<RefCell<GraphInner>>) -> Self {
193 Self { inner }
194 }
195
196 #[inline]
198 pub(crate) fn inner_arc(&self) -> &Rc<RefCell<GraphInner>> {
199 &self.inner
200 }
201
202 #[must_use]
204 pub fn is_valid_name(name: &str) -> bool {
205 !name.contains(PATH_SEP) && !name.starts_with("_anon_")
206 }
207
208 #[must_use]
210 pub fn name(&self) -> String {
211 self.inner.borrow_mut().name.clone()
212 }
213
214 pub fn subscribe_namespace_change(&self, sink: NamespaceChangeSink) -> u64 {
220 register_ns_sink(&self.inner, sink)
221 }
222
223 pub fn unsubscribe_namespace_change(&self, id: u64) {
226 unregister_ns_sink(&self.inner, id);
227 }
228
229 #[must_use]
234 pub fn describe(&self, core: &Core) -> GraphDescribeOutput {
235 describe_of(core, &self.inner, None)
236 }
237
238 #[must_use]
240 pub fn describe_with_debug(
241 &self,
242 core: &Core,
243 debug: &dyn DebugBindingBoundary,
244 ) -> GraphDescribeOutput {
245 describe_of(core, &self.inner, Some(debug))
246 }
247
248 #[must_use = "dropping the handle without calling .detach(&core) leaks the topology sub"]
254 pub fn describe_reactive(&self, core: &Core, sink: &DescribeSink) -> ReactiveDescribeHandle {
255 describe_reactive_in(core, &self.inner, sink)
256 }
257
258 #[must_use = "GraphObserveOne is only useful via .subscribe(...) — dropping it silently no-ops"]
261 pub fn observe(&self, path: &str) -> GraphObserveOne {
262 let id = self.node(path);
263 GraphObserveOne::new(self.clone(), id)
264 }
265
266 #[must_use = "GraphObserveAll is only useful via .subscribe(...) — dropping it silently no-ops"]
268 pub fn observe_all(&self) -> GraphObserveAll {
269 GraphObserveAll::new(self.clone())
270 }
271
272 #[must_use = "GraphObserveAllReactive is only useful via .subscribe(...) — dropping it silently no-ops"]
274 pub fn observe_all_reactive(&self) -> GraphObserveAllReactive {
275 GraphObserveAllReactive::new(self.clone())
276 }
277
278 pub fn fire_namespace_change(&self, core: &Core) {
281 fire_ns(core, &self.inner);
282 }
283
284 pub fn add(
292 &self,
293 core: &Core,
294 node_id: NodeId,
295 name: impl Into<String>,
296 ) -> Result<NodeId, NameError> {
297 let name = name.into();
298 validate_name(&name)?;
299 {
300 let mut inner = self.inner.borrow_mut();
301 if inner.destroyed {
302 return Err(NameError::Destroyed);
303 }
304 if inner.names.contains_key(&name) {
305 return Err(NameError::Collision(name));
306 }
307 inner.names.insert(name.clone(), node_id);
308 inner.names_inverse.insert(node_id, name);
309 }
310 self.fire_namespace_change(core);
311 Ok(node_id)
312 }
313
314 #[must_use]
316 pub fn node(&self, path: &str) -> NodeId {
317 self.try_resolve(path)
318 .unwrap_or_else(|| panic!("Graph::node: no node at path `{path}`"))
319 }
320
321 #[must_use]
323 pub fn try_resolve(&self, path: &str) -> Option<NodeId> {
324 self.try_resolve_checked(path).ok().flatten()
325 }
326
327 pub fn try_resolve_checked(&self, path: &str) -> Result<Option<NodeId>, PathError> {
333 resolve_checked(&self.inner, path)
334 }
335
336 #[must_use]
338 pub fn name_of(&self, node_id: NodeId) -> Option<String> {
339 self.inner.borrow_mut().names_inverse.get(&node_id).cloned()
340 }
341
342 #[must_use]
344 pub fn node_count(&self) -> usize {
345 self.inner.borrow_mut().names.len()
346 }
347
348 #[must_use]
350 pub fn node_names(&self) -> Vec<String> {
351 self.inner.borrow_mut().names.keys().cloned().collect()
352 }
353
354 #[must_use]
356 pub fn child_names(&self) -> Vec<String> {
357 self.inner.borrow_mut().children.keys().cloned().collect()
358 }
359
360 #[must_use]
362 pub fn is_destroyed(&self) -> bool {
363 self.inner.borrow_mut().destroyed
364 }
365
366 pub fn state(
373 &self,
374 core: &Core,
375 name: impl Into<String>,
376 initial: Option<HandleId>,
377 ) -> Result<NodeId, NameError> {
378 let id = core
379 .register_state(initial.unwrap_or(graphrefly_core::NO_HANDLE), false)
380 .expect("invariant: register_state has no error variants reachable for caller-controlled inputs");
381 self.add(core, id, name)
382 }
383
384 pub fn derived(
389 &self,
390 core: &Core,
391 name: impl Into<String>,
392 deps: &[NodeId],
393 fn_id: FnId,
394 equals: EqualsMode,
395 ) -> Result<NodeId, NameError> {
396 let id = core
397 .register_derived(deps, fn_id, equals, false)
398 .expect("invariant: caller has validated dep ids before calling register_derived");
399 self.add(core, id, name)
400 }
401
402 pub fn dynamic(
407 &self,
408 core: &Core,
409 name: impl Into<String>,
410 deps: &[NodeId],
411 fn_id: FnId,
412 equals: EqualsMode,
413 ) -> Result<NodeId, NameError> {
414 let id = core
415 .register_dynamic(deps, fn_id, equals, false)
416 .expect("invariant: caller has validated dep ids before calling register_dynamic");
417 self.add(core, id, name)
418 }
419
420 pub fn set(&self, core: &Core, name: &str, handle: HandleId) {
424 let id = self.node(name);
425 core.emit(id, handle);
426 }
427
428 #[must_use]
430 pub fn get(&self, core: &Core, name: &str) -> HandleId {
431 let id = self.node(name);
432 core.cache_of(id)
433 }
434
435 pub fn invalidate_by_name(&self, core: &Core, name: &str) {
437 let id = self.node(name);
438 core.invalidate(id);
439 }
440
441 pub fn complete_by_name(&self, core: &Core, name: &str) {
443 let id = self.node(name);
444 core.complete(id);
445 }
446
447 pub fn error_by_name(&self, core: &Core, name: &str, error_handle: HandleId) {
449 let id = self.node(name);
450 core.error(id, error_handle);
451 }
452
453 pub fn remove(&self, core: &Core, name: &str) -> Result<GraphRemoveAudit, RemoveError> {
461 {
462 let inner = self.inner.borrow_mut();
463 if inner.destroyed {
464 return Err(RemoveError::Destroyed);
465 }
466 if inner.children.contains_key(name) {
467 drop(inner);
468 return self.unmount(core, name).map_err(|e| match e {
469 MountError::Destroyed => RemoveError::Destroyed,
470 _ => RemoveError::NotFound(name.to_owned()),
471 });
472 }
473 }
474 let node_id = {
475 let inner = self.inner.borrow_mut();
476 if inner.destroyed {
477 return Err(RemoveError::Destroyed);
478 }
479 *inner
480 .names
481 .get(name)
482 .ok_or_else(|| RemoveError::NotFound(name.to_owned()))?
483 };
484 core.teardown(node_id);
485 {
486 let mut inner = self.inner.borrow_mut();
487 inner.names.shift_remove(name);
488 inner.names_inverse.shift_remove(&node_id);
489 }
490 self.fire_namespace_change(core);
491 Ok(GraphRemoveAudit {
492 node_count: 1,
493 mount_count: 0,
494 })
495 }
496
497 #[must_use]
502 pub fn edges(&self, core: &Core, recursive: bool) -> Vec<(String, String)> {
503 let names_map = collect_qualified_names_in(&self.inner, "", recursive);
504 edges_in(core, &self.inner, "", recursive, &names_map)
505 }
506
507 #[must_use = "the SubscriptionId must be kept to later unsubscribe; dropping it leaks the sink"]
513 pub fn subscribe(&self, core: &Core, node_id: NodeId, sink: Sink) -> SubscriptionId {
514 core.subscribe(node_id, sink)
515 }
516
517 pub fn unsubscribe(&self, core: &Core, node_id: NodeId, sub_id: SubscriptionId) {
519 core.unsubscribe(node_id, sub_id);
520 }
521
522 pub fn unsubscribe_topology(&self, core: &Core, id: TopologySubscriptionId) {
524 core.unsubscribe_topology(id);
525 }
526
527 pub fn emit(&self, core: &Core, node_id: NodeId, new_handle: HandleId) {
529 core.emit(node_id, new_handle);
530 }
531
532 #[must_use]
534 pub fn cache_of(&self, core: &Core, node_id: NodeId) -> HandleId {
535 core.cache_of(node_id)
536 }
537
538 #[must_use]
540 pub fn has_fired_once(&self, core: &Core, node_id: NodeId) -> bool {
541 core.has_fired_once(node_id)
542 }
543
544 pub fn complete(&self, core: &Core, node_id: NodeId) {
546 core.complete(node_id);
547 }
548
549 pub fn error(&self, core: &Core, node_id: NodeId, error_handle: HandleId) {
551 core.error(node_id, error_handle);
552 }
553
554 pub fn teardown(&self, core: &Core, node_id: NodeId) {
556 core.teardown(node_id);
557 }
558
559 pub fn invalidate(&self, core: &Core, node_id: NodeId) {
561 core.invalidate(node_id);
562 }
563
564 pub fn pause(&self, core: &Core, node_id: NodeId, lock_id: LockId) -> Result<(), PauseError> {
569 core.pause(node_id, lock_id)
570 }
571
572 pub fn resume(
577 &self,
578 core: &Core,
579 node_id: NodeId,
580 lock_id: LockId,
581 ) -> Result<Option<ResumeReport>, PauseError> {
582 core.resume(node_id, lock_id)
583 }
584
585 #[must_use]
587 pub fn alloc_lock_id(&self, core: &Core) -> LockId {
588 core.alloc_lock_id()
589 }
590
591 pub fn set_deps(
603 &self,
604 core: &Core,
605 n: NodeId,
606 new_deps: &[NodeId],
607 ) -> Result<(), SetDepsError> {
608 core.set_deps(n, new_deps)
609 }
610
611 pub fn set_resubscribable(&self, core: &Core, node_id: NodeId, resubscribable: bool) {
613 core.set_resubscribable(node_id, resubscribable);
614 }
615
616 pub fn add_meta_companion(&self, core: &Core, parent: NodeId, companion: NodeId) {
618 core.add_meta_companion(parent, companion);
619 }
620
621 pub fn batch<F: FnOnce()>(&self, core: &Core, f: F) {
623 core.batch(f);
624 }
625
626 pub fn signal(&self, core: &Core, kind: SignalKind) {
630 match kind {
631 SignalKind::Invalidate => self.signal_invalidate(core),
632 SignalKind::Pause(lock_id) => {
633 for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
634 let _ = core.pause(id, lock_id);
635 }
636 }
637 SignalKind::Resume(lock_id) => {
638 for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
639 let _ = core.resume(id, lock_id);
640 }
641 }
642 SignalKind::Complete => {
643 for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
644 core.complete(id);
645 }
646 }
647 SignalKind::Error(h) => {
648 let ids = collect_signal_ids_with_meta_filter(core, &self.inner);
649 for _ in 1..ids.len() {
652 core.binding_ptr().retain_handle(h);
653 }
654 for id in ids {
655 core.error(id, h);
656 }
657 }
658 }
659 }
660
661 pub fn signal_invalidate(&self, core: &Core) {
665 let to_invalidate = collect_signal_invalidate_ids(core, &self.inner);
666 for id in to_invalidate {
667 core.invalidate(id);
668 }
669 }
670
671 pub fn destroy(&self, core: &Core) {
676 destroy_subtree(core, &self.inner);
677 }
678
679 pub fn mount(
687 &self,
688 core: &Core,
689 name: impl Into<String>,
690 child: &Graph,
691 ) -> Result<Graph, MountError> {
692 crate::mount::mount(core, &self.inner, name.into(), child)
693 }
694
695 pub fn mount_new(&self, core: &Core, name: impl Into<String>) -> Result<Graph, MountError> {
700 crate::mount::mount_new(core, &self.inner, name.into())
701 }
702
703 pub fn mount_with<F: FnOnce(&Graph)>(
708 &self,
709 core: &Core,
710 name: impl Into<String>,
711 builder: F,
712 ) -> Result<Graph, MountError> {
713 let child = self.mount_new(core, name)?;
714 builder(&child);
715 Ok(child)
716 }
717
718 pub fn unmount(&self, core: &Core, name: &str) -> Result<GraphRemoveAudit, MountError> {
723 crate::mount::unmount(core, &self.inner, name)
724 }
725
726 #[must_use]
729 pub fn ancestors(&self, include_self: bool) -> Vec<Graph> {
730 crate::mount::ancestors(&self.inner, include_self)
731 }
732}
733
734fn validate_name(name: &str) -> Result<(), NameError> {
739 if name.contains(PATH_SEP) {
740 Err(NameError::InvalidName(name.to_owned()))
741 } else if name.starts_with("_anon_") {
742 Err(NameError::ReservedPrefix(name.to_owned()))
743 } else {
744 Ok(())
745 }
746}
747
748pub(crate) fn register_ns_sink(
750 inner_arc: &Rc<RefCell<GraphInner>>,
751 sink: NamespaceChangeSink,
752) -> u64 {
753 let mut inner = inner_arc.borrow_mut();
754 let id = inner.next_ns_sink_id;
755 inner.next_ns_sink_id += 1;
756 inner.namespace_sinks.insert(id, sink);
757 id
758}
759
760pub(crate) fn unregister_ns_sink(inner_arc: &Rc<RefCell<GraphInner>>, id: u64) {
762 inner_arc.borrow_mut().namespace_sinks.shift_remove(&id);
763}
764
765pub(crate) fn resolve_checked(
767 inner_arc: &Rc<RefCell<GraphInner>>,
768 path: &str,
769) -> Result<Option<NodeId>, PathError> {
770 if path.is_empty() {
771 return Err(PathError::Empty);
772 }
773 let inner = inner_arc.borrow_mut();
774 if inner.destroyed {
775 return Err(PathError::Destroyed);
776 }
777 let segments: Vec<&str> = path.split(PATH_SEP).collect();
778 let first = segments[0];
779 if first == ".." {
780 let parent_weak = inner.parent.as_ref().ok_or(PathError::NoParent)?;
781 let parent_inner = parent_weak.upgrade().ok_or(PathError::NoParent)?;
782 drop(inner);
783 if segments.len() == 1 {
784 return Ok(None);
785 }
786 let rest = segments[1..].join(PATH_SEP);
787 resolve_checked(&parent_inner, &rest)
788 } else if segments.len() > 1 {
789 let child = inner
790 .children
791 .get(first)
792 .cloned()
793 .ok_or_else(|| PathError::ChildNotFound(first.to_string()))?;
794 drop(inner);
795 let rest = segments[1..].join(PATH_SEP);
796 resolve_checked(&child, &rest)
797 } else {
798 Ok(inner.names.get(first).copied())
799 }
800}
801
802pub(crate) fn fire_ns(core: &Core, inner_arc: &Rc<RefCell<GraphInner>>) {
805 let sinks: Vec<NamespaceChangeSink> = {
806 let inner = inner_arc.borrow_mut();
807 inner.namespace_sinks.values().cloned().collect()
808 };
809 for sink in sinks {
810 sink(core);
811 }
812}
813
814pub(crate) fn destroy_subtree(core: &Core, inner_arc: &Rc<RefCell<GraphInner>>) {
817 let (own_ids, child_clones) = {
818 let mut inner = inner_arc.borrow_mut();
819 if inner.destroyed {
820 return; }
822 inner.destroyed = true;
823 let own = inner.names.values().copied().collect::<Vec<_>>();
824 let kids = inner.children.values().cloned().collect::<Vec<_>>();
825 (own, kids)
826 };
827 for child in &child_clones {
828 destroy_subtree(core, child);
829 }
830 for id in own_ids {
831 core.teardown(id);
832 }
833 {
834 let mut inner = inner_arc.borrow_mut();
835 inner.names.clear();
836 inner.names_inverse.clear();
837 inner.children.clear();
838 }
839 fire_ns(core, inner_arc);
843 inner_arc.borrow_mut().namespace_sinks.clear();
851}
852
853fn collect_signal_ids_with_meta_filter(core: &Core, root: &Rc<RefCell<GraphInner>>) -> Vec<NodeId> {
857 let mut out: Vec<NodeId> = Vec::new();
858 let mut worklist: Vec<Rc<RefCell<GraphInner>>> = vec![root.clone()];
859 while let Some(inner_arc) = worklist.pop() {
860 let (own_ids, meta_set, child_clones) = {
861 let inner = inner_arc.borrow_mut();
862 if inner.destroyed {
863 continue;
864 }
865 let mut meta_set: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
866 for &parent_id in inner.names.values() {
867 for child_id in core.meta_companions_of(parent_id) {
868 meta_set.insert(child_id);
869 }
870 }
871 (
872 inner.names.values().copied().collect::<Vec<_>>(),
873 meta_set,
874 inner.children.values().cloned().collect::<Vec<_>>(),
875 )
876 };
877 for id in own_ids {
878 if meta_set.contains(&id) {
879 continue;
880 }
881 out.push(id);
882 }
883 worklist.extend(child_clones);
884 }
885 out
886}
887
888fn collect_signal_invalidate_ids(core: &Core, root: &Rc<RefCell<GraphInner>>) -> Vec<NodeId> {
890 let mut out: Vec<NodeId> = Vec::new();
891 let mut worklist: Vec<Rc<RefCell<GraphInner>>> = vec![root.clone()];
892 while let Some(inner_arc) = worklist.pop() {
893 let (own_ids, meta_set, child_clones) = {
894 let inner = inner_arc.borrow_mut();
895 if inner.destroyed {
896 continue;
897 }
898 let mut meta_set: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
899 for &parent_id in inner.names.values() {
900 for child_id in core.meta_companions_of(parent_id) {
901 meta_set.insert(child_id);
902 }
903 }
904 (
905 inner.names.values().copied().collect::<Vec<_>>(),
906 meta_set,
907 inner.children.values().cloned().collect::<Vec<_>>(),
908 )
909 };
910 for id in own_ids {
911 if meta_set.contains(&id) {
912 continue;
913 }
914 out.push(id);
915 }
916 worklist.extend(child_clones);
917 }
918 out
919}
920
921pub(crate) fn collect_qualified_names_in(
924 inner_arc: &Rc<RefCell<GraphInner>>,
925 prefix: &str,
926 recursive: bool,
927) -> IndexMap<NodeId, String> {
928 let inner = inner_arc.borrow_mut();
929 let mut map: IndexMap<NodeId, String> = inner
930 .names
931 .iter()
932 .map(|(n, id)| (*id, format!("{prefix}{n}")))
933 .collect();
934 let children: Vec<(String, Rc<RefCell<GraphInner>>)> = if recursive {
935 inner
936 .children
937 .iter()
938 .map(|(n, g)| (n.clone(), g.clone()))
939 .collect()
940 } else {
941 Vec::new()
942 };
943 drop(inner);
944 for (child_name, child_inner) in children {
945 let child_prefix = format!("{prefix}{child_name}::");
946 let child_map = collect_qualified_names_in(&child_inner, &child_prefix, true);
947 for (id, name) in child_map {
948 map.entry(id).or_insert(name);
949 }
950 }
951 map
952}
953
954pub(crate) fn edges_in(
956 core: &Core,
957 inner_arc: &Rc<RefCell<GraphInner>>,
958 prefix: &str,
959 recursive: bool,
960 names_map: &IndexMap<NodeId, String>,
961) -> Vec<(String, String)> {
962 let inner = inner_arc.borrow_mut();
963 let qualified: Vec<(String, NodeId)> = inner
964 .names
965 .iter()
966 .map(|(n, id)| (format!("{prefix}{n}"), *id))
967 .collect();
968 let children: Vec<(String, Rc<RefCell<GraphInner>>)> = if recursive {
969 inner
970 .children
971 .iter()
972 .map(|(n, g)| (n.clone(), g.clone()))
973 .collect()
974 } else {
975 Vec::new()
976 };
977 drop(inner);
978 let mut result: Vec<(String, String)> = Vec::new();
979 for (to_name, id) in &qualified {
980 let dep_ids = core.deps_of(*id);
981 for dep_id in dep_ids {
982 let from_name = names_map
983 .get(&dep_id)
984 .cloned()
985 .unwrap_or_else(|| format!("{prefix}_anon_{}", dep_id.raw()));
986 result.push((from_name, to_name.clone()));
987 }
988 }
989 for (child_name, child_inner) in children {
990 let child_prefix = format!("{prefix}{child_name}::");
991 result.extend(edges_in(core, &child_inner, &child_prefix, true, names_map));
992 }
993 result
994}
995
996