use petgraph::{
Direction,
graph::NodeIndex,
visit::{DfsPostOrder, EdgeFiltered},
};
use rustc_hash::FxHashSet;
use crate::ir::{
GraphInlineType,
graph::{CookedGraph, EdgeKind},
types::{GraphSchemaType, GraphStruct, GraphStructField, GraphType, StructFieldName},
};
use super::{ViewNode, 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
}
pub fn fields(&self) -> impl Iterator<Item = StructFieldView<'_, 'a>> {
let inherits = EdgeFiltered::from_fn(&self.cooked.graph, |e| {
matches!(e.weight(), EdgeKind::Inherits)
});
let mut dfs = DfsPostOrder::new(&inherits, self.index);
let ancestors = std::iter::from_fn(move || dfs.next(&inherits))
.filter(move |&index| index != self.index)
.filter_map(|index| match self.cooked.graph[index] {
GraphType::Schema(GraphSchemaType::Struct(_, s))
| GraphType::Inline(GraphInlineType::Struct(_, s)) => Some(s.fields),
GraphType::Schema(GraphSchemaType::Tagged(_, t))
| GraphType::Inline(GraphInlineType::Tagged(_, t)) => Some(t.fields),
GraphType::Schema(GraphSchemaType::Untagged(_, u))
| GraphType::Inline(GraphInlineType::Untagged(_, u)) => Some(u.fields),
_ => None,
});
let mut seen: FxHashSet<_> = self.ty.fields.iter().map(|field| field.name).collect();
itertools::chain!(
ancestors
.flatten()
.filter(move |field| seen.insert(field.name))
.map(|field| StructFieldView {
parent: self,
field,
inherited: true,
}),
self.own_fields(),
)
}
#[inline]
pub fn own_fields(&self) -> impl Iterator<Item = StructFieldView<'_, 'a>> {
self.ty.fields.iter().map(move |field| StructFieldView {
parent: self,
field,
inherited: false,
})
}
#[inline]
pub fn parents(&self) -> impl Iterator<Item = TypeView<'a>> {
self.ty
.parents
.iter()
.map(move |&parent| TypeView::new(self.cooked, parent))
}
}
impl<'a> ViewNode<'a> for StructView<'a> {
#[inline]
fn cooked(&self) -> &'a CookedGraph<'a> {
self.cooked
}
#[inline]
fn index(&self) -> NodeIndex<usize> {
self.index
}
}
#[derive(Debug)]
pub struct StructFieldView<'view, 'a> {
parent: &'view StructView<'a>,
field: &'a GraphStructField<'a>,
inherited: bool,
}
impl<'view, 'a> StructFieldView<'view, 'a> {
#[inline]
pub fn name(&self) -> StructFieldName<'a> {
self.field.name
}
#[inline]
pub fn ty(&self) -> TypeView<'a> {
TypeView::new(self.parent.cooked, self.field.ty)
}
#[inline]
pub fn required(&self) -> bool {
self.field.required
}
#[inline]
pub fn description(&self) -> Option<&'a str> {
self.field.description
}
#[inline]
pub fn tag(&self) -> bool {
let StructFieldName::Name(name) = self.field.name else {
return false;
};
self.parent
.cooked
.graph
.neighbors_directed(self.parent.index, Direction::Incoming)
.filter_map(|index| match self.parent.cooked.graph[index] {
GraphType::Schema(GraphSchemaType::Tagged(_, tagged))
| GraphType::Inline(GraphInlineType::Tagged(_, tagged)) => Some(tagged),
_ => None,
})
.any(|neighbor| neighbor.tag == name)
}
#[inline]
pub fn flattened(&self) -> bool {
self.field.flattened
}
#[inline]
pub fn inherited(&self) -> bool {
self.inherited
}
#[inline]
pub fn needs_indirection(&self) -> bool {
let graph = self.parent.cooked;
graph.metadata.scc_indices[self.parent.index.index()]
== graph.metadata.scc_indices[self.field.ty.index()]
}
}