use fixedbitset::FixedBitSet;
use itertools::Itertools;
use petgraph::{
Direction,
graph::NodeIndex,
visit::{EdgeFiltered, EdgeRef, IntoNeighbors},
};
use rustc_hash::FxHashSet;
use crate::ir::{
graph::{CookedGraph, GraphEdge},
types::{FieldMeta, GraphInlineType, GraphSchemaType, GraphStruct, GraphType, StructFieldName},
};
use super::{ViewNode, container::ContainerView, ir::TypeView};
#[derive(Debug)]
pub struct StructView<'a> {
cooked: &'a CookedGraph<'a>,
index: NodeIndex<usize>,
ty: GraphStruct<'a>,
}
impl<'a> StructView<'a> {
#[inline]
pub(in crate::ir) fn new(
cooked: &'a CookedGraph<'a>,
index: NodeIndex<usize>,
ty: GraphStruct<'a>,
) -> Self {
Self { cooked, index, ty }
}
#[inline]
pub fn description(&self) -> Option<&'a str> {
self.ty.description
}
#[inline]
pub fn fields(&self) -> impl Iterator<Item = StructFieldView<'_, 'a>> {
let all = self
.inherited_fields() .chain(self.own_fields())
.collect_vec();
let mut seen = FxHashSet::default();
let deduped = all
.into_iter()
.rev()
.filter(|f| seen.insert(f.meta.name))
.collect_vec();
deduped.into_iter().rev()
}
fn inherited_fields(&self) -> impl Iterator<Item = StructFieldView<'_, 'a>> {
enum Step {
Enter(NodeIndex<usize>),
Exit(NodeIndex<usize>),
}
let inherits = EdgeFiltered::from_fn(&self.cooked.graph, |e| {
matches!(e.weight(), GraphEdge::Inherits { .. })
});
let mut stack = vec![Step::Enter(self.index)];
let mut visited = FixedBitSet::with_capacity(self.cooked.graph.node_count());
let mut ancestors = vec![];
while let Some(step) = stack.pop() {
match step {
Step::Enter(node) => {
if visited.put(node.index()) {
continue;
}
stack.push(Step::Exit(node));
stack.extend(
inherits
.neighbors(node) .collect_vec()
.into_iter()
.rev()
.map(Step::Enter),
);
}
Step::Exit(node) if node != self.index => {
ancestors.push(node);
}
_ => {}
}
}
ancestors
.into_iter()
.flat_map(|index| self.cooked.fields(index))
.map(|info| StructFieldView::new(self, info.meta, info.target, true))
}
#[inline]
pub fn own_fields(&self) -> impl Iterator<Item = StructFieldView<'_, 'a>> {
self.cooked
.fields(self.index)
.map(move |info| StructFieldView::new(self, info.meta, info.target, false))
}
#[inline]
pub fn parents(&self) -> impl Iterator<Item = TypeView<'a>> {
self.cooked
.inherits(self.index)
.map(move |info| TypeView::new(self.cooked, info.target))
}
}
impl<'a> ViewNode<'a> for StructView<'a> {
#[inline]
fn cooked(&self) -> &'a CookedGraph<'a> {
self.cooked
}
#[inline]
fn index(&self) -> NodeIndex<usize> {
self.index
}
}
pub type StructFieldView<'view, 'a> = FieldView<'view, 'a, StructView<'a>>;
#[derive(Debug)]
pub struct FieldView<'view, 'a, P> {
parent: &'view P,
meta: FieldMeta<'a>,
ty: NodeIndex<usize>,
inherited: bool,
}
#[allow(private_bounds, reason = "`ViewNode` is sealed")]
impl<'view, 'a, P: ViewNode<'a>> FieldView<'view, 'a, P> {
#[inline]
pub(in crate::ir) fn new(
parent: &'view P,
meta: FieldMeta<'a>,
ty: NodeIndex<usize>,
inherited: bool,
) -> Self {
Self {
parent,
meta,
ty,
inherited,
}
}
#[inline]
pub fn name(&self) -> StructFieldName<'a> {
self.meta.name
}
#[inline]
pub fn ty(&self) -> TypeView<'a> {
TypeView::new(self.parent.cooked(), self.ty)
}
#[inline]
pub fn required(&self) -> Required {
if self.meta.required {
let nullable = matches!(self.ty().as_container(), Some(ContainerView::Optional(_)));
Required::Required { nullable }
} else {
Required::Optional
}
}
#[inline]
pub fn description(&self) -> Option<&'a str> {
self.meta.description
}
#[inline]
pub fn flattened(&self) -> bool {
self.meta.flattened
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Required {
Required {
nullable: bool,
},
Optional,
}
impl<'view, 'a> FieldView<'view, 'a, StructView<'a>> {
#[inline]
pub fn inherited(&self) -> bool {
self.inherited
}
#[inline]
pub fn tag(&self) -> bool {
let StructFieldName::Name(name) = self.meta.name else {
return false;
};
let cooked = self.parent.cooked();
cooked
.graph
.edges_directed(self.parent.index(), Direction::Incoming)
.filter(|e| {
matches!(
e.weight(),
GraphEdge::Variant(_) | GraphEdge::Inherits { .. }
)
})
.filter_map(|e| match cooked.graph[e.source()] {
GraphType::Schema(GraphSchemaType::Tagged(_, tagged))
| GraphType::Inline(GraphInlineType::Tagged(_, tagged)) => Some(tagged),
_ => None,
})
.any(|neighbor| neighbor.tag == name)
}
#[inline]
pub fn needs_box(&self) -> bool {
let graph = self.parent.cooked();
graph.metadata.box_sccs[self.parent.index().index()]
== graph.metadata.box_sccs[self.ty.index()]
}
}