Skip to main content

guppy/graph/feature/
weak.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Support for weak features.
5//!
6//! A weak feature such as `a = ["foo?/std"]` is a single edge in the feature
7//! graph for each package `foo` resolves to (usually one), but Cargo applies
8//! it to each declaration of `foo` separately. guppy models that by splitting
9//! each edge's link into two halves, and running the traversal through the
10//! buffered edge filter in [`crate::petgraph_support::dfs`]:
11//!
12//! * The half covering `foo`'s required declarations is offered to the visitor
13//!   as soon as the edge is reached. It is absent if `foo` has no required
14//!   declaration.
15//! * The half covering `foo`'s optional declarations is held in a buffer in a
16//!   forward query until `dep:foo` is activated, and offered then. If
17//!   `dep:foo` is never reached, that half is never offered. In a reverse
18//!   query, it is offered as soon as the edge is reached.
19//!
20//! The edge is followed if the visitor accepts either half.
21
22use crate::graph::{
23    DependencyDirection, FeatureIx, PackageIx,
24    feature::{ConditionalLink, EdgeLinks, FeatureEdgeReference},
25};
26use ahash::AHashMap;
27use indexmap::IndexSet;
28use petgraph::graph::{EdgeIndex, NodeIndex};
29use smallvec::SmallVec;
30
31/// Data structure that tracks pairs of package indexes that form weak dependencies.
32#[derive(Clone, Debug)]
33pub(super) struct WeakDependencies {
34    ixs: IndexSet<EdgeIndex<PackageIx>>,
35    // A map of dep:foo node to the weak indexes it releases when reached.
36    by_optional_dependency: AHashMap<NodeIndex<FeatureIx>, SmallVec<[WeakIndex; 1]>>,
37}
38
39impl WeakDependencies {
40    pub(super) fn new() -> Self {
41        Self {
42            ixs: IndexSet::new(),
43            by_optional_dependency: AHashMap::new(),
44        }
45    }
46
47    pub(super) fn insert(
48        &mut self,
49        edge_ix: EdgeIndex<PackageIx>,
50        optional_dependency_ix: NodeIndex<FeatureIx>,
51    ) -> WeakIndex {
52        let (index, inserted) = self.ixs.insert_full(edge_ix);
53        let index = WeakIndex(index);
54        if inserted {
55            self.by_optional_dependency
56                .entry(optional_dependency_ix)
57                .or_default()
58                .push(index);
59        }
60        index
61    }
62}
63
64// Not part of the public API -- exposed for testing.
65#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
66#[doc(hidden)]
67pub struct WeakIndex(pub(super) usize);
68
69/// Buffer states for weak indexes, to be used during a feature resolver traversal.
70pub(super) struct WeakBufferStates<'g, 'a, F> {
71    buffers: WeakBuffers<'g, 'a>,
72    accept_fn: F,
73}
74
75/// The buffers a traversal keeps for the optional halves of weak edges.
76enum WeakBuffers<'g, 'a> {
77    /// Buffering is enabled. Used by forward queries.
78    ///
79    /// A buffer is released when the traversal discovers `dep:foo`, which is
80    /// when the dependent activates `foo`'s optional declarations.
81    PerPackageEdge {
82        /// The weak dependencies that map a `dep:foo` node back to the indexes
83        /// it releases.
84        deps: &'a WeakDependencies,
85
86        /// A buffer for each weak index.
87        states: Vec<SingleBufferState<'g>>,
88    },
89
90    /// No weak buffers: optional links are offered as soon as they are reached.
91    /// Used by reverse queries.
92    Unbuffered,
93}
94
95impl<'g, 'a, F> WeakBufferStates<'g, 'a, F>
96where
97    F: FnMut(ConditionalLink<'g>) -> bool,
98{
99    /// Returns buffer states for a traversal in `direction`.
100    #[inline]
101    pub(super) fn new(
102        deps: &'a WeakDependencies,
103        direction: DependencyDirection,
104        accept_fn: F,
105    ) -> Self {
106        let buffers = match direction {
107            DependencyDirection::Forward => {
108                let len = deps.ixs.len();
109                let mut states = Vec::with_capacity(len);
110                states.resize_with(len, || SingleBufferState::Buffered(SingleBufferVec::new()));
111                WeakBuffers::PerPackageEdge { deps, states }
112            }
113            DependencyDirection::Reverse => WeakBuffers::Unbuffered,
114        };
115        Self { buffers, accept_fn }
116    }
117
118    pub(super) fn track(
119        &mut self,
120        edge_ref: FeatureEdgeReference<'g>,
121        links: EdgeLinks<'g>,
122    ) -> Option<FeatureEdgeReference<'g>> {
123        let accepted = match links {
124            EdgeLinks::Weak {
125                required,
126                optional,
127                index,
128            } => {
129                // Handle both halves, required first.
130                //
131                // Both halves must be dealt with before they are combined, so
132                // don't fold these into a single `a || b` expression. If the
133                // required half is accepted, a short-circuiting `||` would skip
134                // the optional half, and the visitor would never see it.
135                //
136                // A buffered optional half is held along with `edge_ref` even
137                // when the required half was just accepted, so releasing the
138                // buffer can hand the same `edge_ref` to the traversal a second
139                // time. That is harmless: the DFS checks its discovered set
140                // before pushing a target.
141                let required_accepted = required.is_some_and(|required| (self.accept_fn)(required));
142                let optional_accepted = match &mut self.buffers {
143                    WeakBuffers::PerPackageEdge { deps: _, states } => match &mut states[index.0] {
144                        SingleBufferState::Buffered(buffer) => {
145                            buffer.push((optional, edge_ref));
146                            false
147                        }
148                        SingleBufferState::Released => (self.accept_fn)(optional),
149                    },
150                    WeakBuffers::Unbuffered => (self.accept_fn)(optional),
151                };
152                required_accepted || optional_accepted
153            }
154            EdgeLinks::NonWeak(link) => (self.accept_fn)(link),
155        };
156        accepted.then_some(edge_ref)
157    }
158
159    // Called when the DFS reaches a feature node.
160    pub(super) fn discover(
161        &mut self,
162        feature_ix: NodeIndex<FeatureIx>,
163    ) -> Vec<FeatureEdgeReference<'g>> {
164        let (deps, states) = match &mut self.buffers {
165            WeakBuffers::PerPackageEdge { deps, states } => (*deps, states),
166            WeakBuffers::Unbuffered => return Vec::new(),
167        };
168        let Some(weak_indexes) = deps.by_optional_dependency.get(&feature_ix) else {
169            return Vec::new();
170        };
171
172        release_buffers(states, weak_indexes, &mut self.accept_fn)
173    }
174}
175
176fn release_buffers<'g, F>(
177    states: &mut [SingleBufferState<'g>],
178    weak_indexes: &[WeakIndex],
179    accept_fn: &mut F,
180) -> Vec<FeatureEdgeReference<'g>>
181where
182    F: FnMut(ConditionalLink<'g>) -> bool,
183{
184    let mut released = Vec::new();
185    for weak_index in weak_indexes {
186        match std::mem::replace(&mut states[weak_index.0], SingleBufferState::Released) {
187            SingleBufferState::Buffered(buffer) => {
188                // Transition from buffered to released.
189                released.extend(buffer.into_iter().filter_map(|(link, edge_ref)| {
190                    // Filter buffered links.
191                    accept_fn(link).then_some(edge_ref)
192                }));
193            }
194            SingleBufferState::Released => {
195                // This should never happen, since nodes are discovered at
196                // most once.
197                debug_assert!(false, "weak index {weak_index:?} is released once");
198            }
199        }
200    }
201    released
202}
203
204/// Buffer state for a single weak index in an in-progress resolver.
205enum SingleBufferState<'g> {
206    /// The optional halves seen so far, held until the buffer is released.
207    Buffered(SingleBufferVec<'g>),
208
209    /// The buffer has been released: optional halves are offered to the
210    /// visitor as they are reached.
211    Released,
212}
213
214type SingleBufferVec<'g> = Vec<(ConditionalLink<'g>, FeatureEdgeReference<'g>)>;