blob: d127dcf7bc580e025443e0d567082ecc3df7f7de [file]
// Copyright 2026 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use starlark::{
collections::SmallSet,
values::{ProvidesStaticType, UnpackValue as _, Value, ValueLike},
};
use crate::{
depset::{Depset, DepsetGen, Order},
unpack::UnpackDepset,
};
/// An iterator over the elements of a depset, respecting its traversal order.
pub struct DepsetIterator<'a, 'v> {
stack: Vec<IterState<'a, 'v>>,
visited_nodes: std::collections::HashSet<usize>,
visited_elements: SmallSet<Value<'v>>,
order: Order,
topo_buffer: Option<Vec<Value<'v>>>,
}
enum IterState<'a, 'v> {
Direct {
iter: std::slice::Iter<'a, Value<'v>>,
},
Transitive {
iter: std::slice::Iter<'a, Value<'v>>,
},
}
impl<'a, 'v> DepsetIterator<'a, 'v> {
fn push_depset(&mut self, depset: &'a Depset<'v>, order: Order) {
if !depset.is_empty() {
match order {
Order::Preorder => {
self.stack.push(IterState::Transitive {
iter: depset.transitive().iter(),
});
self.stack.push(IterState::Direct {
iter: depset.direct().iter(),
});
},
Order::Postorder | Order::Unspecified => {
self.stack.push(IterState::Direct {
iter: depset.direct().iter(),
});
self.stack.push(IterState::Transitive {
iter: depset.transitive().iter(),
});
},
Order::Topological => unreachable!(),
}
}
}
}
impl<'a, 'v> Iterator for DepsetIterator<'a, 'v>
where
'v: 'a,
{
type Item = Value<'v>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(state) = self.stack.last_mut() {
match state {
IterState::Direct { iter } => {
if let Some(&val) = iter.next() {
let hash = val.get_hashed().expect("Already verified hashable");
if self.visited_elements.insert_hashed(hash) {
return Some(val);
}
} else {
self.stack.pop();
}
},
IterState::Transitive { iter } => {
if let Some(&child_val) = iter.next() {
if let Some(child_depset) =
UnpackDepset::unpack_value(child_val).ok().flatten()
{
let child_depset: &'v Depset<'v> = child_depset.depset();
let node_id = std::ptr::from_ref::<Depset<'v>>(child_depset) as usize;
if self.visited_nodes.insert(node_id) {
self.push_depset(child_depset, self.order);
}
}
} else {
self.stack.pop();
}
},
}
}
if self.order == Order::Topological {
// Safety: Topo order should always have topo buffer set
return unsafe { self.topo_buffer.as_mut().unwrap_unchecked() }.pop();
}
None
}
}
impl<'v, V: ValueLike<'v>> DepsetGen<V>
where
V: starlark::coerce::Coerce<Value<'v>>,
Self: ProvidesStaticType<'v>,
{
fn iter_ordered<'a>(&'a self, order: Order) -> DepsetIterator<'a, 'v>
where
'v: 'a,
{
let mut iter = DepsetIterator {
stack: Vec::new(),
visited_nodes: std::collections::HashSet::new(),
visited_elements: Default::default(),
order,
// Topological order requires complete collection before yielding any elements.
// We just do reverse postorder by collecting postorder to a vec and having iter
// call vec.pop().
topo_buffer: if order == Order::Topological {
Some(self.iter_ordered(Order::Postorder).collect())
} else {
None
},
};
if order != Order::Topological {
let depset: &Depset<'v> = starlark::coerce::coerce(self);
iter.visited_nodes
.insert(std::ptr::from_ref::<Depset<'v>>(depset) as usize);
iter.push_depset(depset, order);
}
iter
}
/// Returns an iterator over the elements in the depset using its configured
/// order.
pub fn iter<'a>(&'a self) -> DepsetIterator<'a, 'v>
where
'v: 'a,
{
self.iter_ordered(self.order())
}
}
#[cfg(test)]
mod tests {
use crate::globals::tests::new_assert;
#[test]
fn test_depset() {
let mut a = new_assert();
a.equivalent(
"depset(['c'], transitive=[depset(['a', 'b'])], order='preorder').to_list()",
"['c', 'a', 'b']",
);
a.equivalent(
"depset(['c'], transitive=[depset(['a', 'b'])], order='postorder').to_list()",
"['a', 'b', 'c']",
);
a.equivalent(
"depset(['c'], transitive=[depset(['a', 'b'])], order='topological').to_list()",
"['c', 'b', 'a']",
);
}
#[test]
fn test_depset_complex_graph() {
let mut a = new_assert();
a.equivalent(
r#"
d1 = depset(['a'])
d2 = depset(['b', 'c'], transitive=[d1])
d3 = depset(['d'], transitive=[d1])
depset(['e'], transitive=[d2, d3], order='preorder').to_list()
"#,
"['e', 'b', 'c', 'a', 'd']",
);
a.equivalent(
r#"
d1 = depset(['a'])
d2 = depset(['b', 'c'], transitive=[d1])
d3 = depset(['d'], transitive=[d1])
depset(['e'], transitive=[d2, d3], order='postorder').to_list()
"#,
"['a', 'b', 'c', 'd', 'e']",
);
a.equivalent(
r#"
d1 = depset(['a'])
d2 = depset(['b', 'c'], transitive=[d1])
d3 = depset(['d'], transitive=[d1])
depset(['e'], transitive=[d2, d3], order='topological').to_list()
"#,
"['e', 'd', 'c', 'b', 'a']",
);
}
}