tamer: asg::graph: Strict static enforcement of tree/cross edge spans

We are now able to have confidence that the graph is properly constructed
with or without a reference span depending on whether the edge is a tree or
cross edge.  Consequently, the span is now a reliable indicator of whether
an edge is tree or cross in _all_ cases, not just in dynamic ones.

In fact, this did catch a couple cases where the spans were _not_ properly
applied.

This will in turn give me confidence moving forward with static analysis
based on the graph (and edge hooks).

I could go further than this by introducing a new span type in place of
`Option<Span>`, which would also allow me to get rid of having two methods
on `Asg`, but I want to move on for now; this can be cleaned up more later
on.

It's also worth noting that this explicit method-based distinction between
edge types also means that each caller will carefully consider how the
operation affects the graph.  Previously, that consideration was framed very
differently: "do I need a contextual span or not?".  That's not the right
question to ask.

DEV-13163
main
Mike Gerwitz 2023-08-01 01:32:28 -04:00
parent c0ba827d90
commit 9f74c0fc92
12 changed files with 330 additions and 86 deletions

View File

@ -863,7 +863,7 @@ impl AirAggregateCtx {
},
)?;
oi_pkg.root(&mut self.asg)?;
oi_pkg.root_tree(&mut self.asg)?;
self.ooi_pkg.replace(oi_pkg);
Ok(oi_pkg)
@ -921,7 +921,7 @@ impl AirAggregateCtx {
/// A value of [`None`] indicates that expressions are not permitted to
/// dangle in the current context
/// (and so must be identified).
fn dangling_expr_oi(&self) -> Option<ObjectIndexTo<Expr>> {
fn dangling_expr_oi(&self) -> Option<ObjectIndexToTree<Expr>> {
use AirAggregate::*;
self.stack.iter().rev().find_map(|st| match st {
@ -963,7 +963,7 @@ impl AirAggregateCtx {
///
/// A value of [`None`] indicates that template expansion is not
/// permitted in this current context.
fn expansion_oi(&self) -> Option<ObjectIndexTo<Tpl>> {
fn expansion_oi(&self) -> Option<ObjectIndexToTree<Tpl>> {
use AirAggregate::*;
self.stack.iter().rev().find_map(|st| match st {

View File

@ -30,7 +30,7 @@ use super::{
AirAggregate, AirAggregateCtx,
};
use crate::{
asg::{graph::object::ObjectIndexTo, ObjectKind},
asg::{graph::object::ObjectIndexToTree, ObjectKind},
f::Map,
parse::prelude::*,
};
@ -204,7 +204,7 @@ impl AirExprAggregate {
/// then an [`AsgError::DanglingExpr`] will be returned.
fn hold_dangling(
asg: &mut Asg,
oi_root: Option<ObjectIndexTo<Expr>>,
oi_root: Option<ObjectIndexToTree<Expr>>,
oi_expr: ObjectIndex<Expr>,
) -> Result<(), AsgError> {
let oi_container = oi_root

View File

@ -88,7 +88,7 @@ impl ParseState for AirOpaqueAggregate {
(Ready, IdentRoot(name)) => ctx
.lookup_lexical_or_missing(name)
.root(ctx.asg_mut())
.root_cross(ctx.asg_mut())
.map(|_| ())
.transition(Ready),
}

View File

@ -22,8 +22,8 @@
//! ![Visualization of ASG ontology](../ontviz.svg)
use self::object::{
DynObjectRel, ObjectIndexRelTo, ObjectRelFrom, ObjectRelTy,
ObjectRelatable, Root,
DynObjectRel, ObjectIndexCrossRelTo, ObjectIndexRelTo,
ObjectIndexTreeRelTo, ObjectRelFrom, ObjectRelTy, ObjectRelatable, Root,
};
use super::{AsgError, Object, ObjectIndex, ObjectKind};
@ -190,32 +190,74 @@ impl Asg {
ObjectIndex::new(node_id, span)
}
/// Add an edge from the [`Object`] represented by the
/// Add a tree edge from the [`Object`] represented by the
/// [`ObjectIndex`] `from_oi` to the object represented by `to_oi`.
///
/// The edge may optionally contain a _contextual [`Span`]_,
/// in cases where it is important to distinguish between the span
/// associated with the target and the span associated with the
/// _reference_ to the target.
/// A tree edge represents _ownership_ over the target object
/// (`from_oi` owns `to_oi`),
/// forming a tree parent/child relationships between objects.
/// To reference objects from other trees
/// (crossing trees),
/// see [`Self::add_cross_edge`].
///
/// For more information on how the ASG's ontology is enforced statically,
/// see [`ObjectRelTo`](object::ObjectRelTo).
/// see [`object`].
///
/// Callers external to this module should use [`ObjectIndex`] APIs to
/// manipulate the graph;
/// this allows those objects to uphold their own invariants
/// relative to the state of the graph.
fn add_edge<OA: ObjectIndexRelTo<OB>, OB: ObjectKind + ObjectRelatable>(
fn add_tree_edge<
OA: ObjectIndexTreeRelTo<OB>,
OB: ObjectKind + ObjectRelatable,
>(
&mut self,
from_oi: OA,
to_oi: ObjectIndex<OB>,
ref_span: Option<Span>,
) -> Result<(), AsgError> {
from_oi.pre_add_edge(self, to_oi, ref_span, |asg| {
from_oi.pre_add_edge(self, to_oi, None, |asg| {
asg.graph.add_edge(
from_oi.widen().into(),
to_oi.into(),
(from_oi.src_rel_ty(), OB::rel_ty(), ref_span),
(from_oi.src_rel_ty(), OB::rel_ty(), None),
);
})
}
/// Add a cross edge from the [`Object`] represented by the
/// [`ObjectIndex`] `from_oi` to the object represented by `to_oi`.
///
/// A cross edge represents a _reference_ to another object,
/// crossing into another tree.
/// To indicate ownership,
/// see [`Self::add_tree_edge`].
///
/// The edge must contain a _reference [`Span`]_,
/// which represents the location of the reference _to_ the
/// object `to_oi`,
/// rather than the object itself.
///
/// For more information on how the ASG's ontology is enforced statically,
/// see [`object`].
///
/// Callers external to this module should use [`ObjectIndex`] APIs to
/// manipulate the graph;
/// this allows those objects to uphold their own invariants
/// relative to the state of the graph.
fn add_cross_edge<
OA: ObjectIndexCrossRelTo<OB>,
OB: ObjectKind + ObjectRelatable,
>(
&mut self,
from_oi: OA,
to_oi: ObjectIndex<OB>,
ref_span: Span,
) -> Result<(), AsgError> {
from_oi.pre_add_edge(self, to_oi, Some(ref_span), |asg| {
asg.graph.add_edge(
from_oi.widen().into(),
to_oi.into(),
(from_oi.src_rel_ty(), OB::rel_ty(), Some(ref_span)),
);
})
}
@ -409,9 +451,11 @@ fn diagnostic_node_missing_desc<O: ObjectKind>(
///
/// How Does This Work With Trait Specialization?
/// =============================================
/// [`Asg::add_edge`] is provided a [`ObjectIndexRelTo`],
/// which needs narrowing to an appropriate source [`ObjectKind`] so that
/// we can invoke [`<O as AsgRelMut>::pre_add_edge`](AsgRelMut::pre_add_edge).
/// [`Asg::add_tree_edge`]/[`Asg::add_cross_edge`] is provided a
/// [`ObjectIndexRelTo`],
/// which needs narrowing to an appropriate source [`ObjectKind`] so
/// that we can invoke
/// [`<O as AsgRelMut>::pre_add_edge`](AsgRelMut::pre_add_edge).
///
/// At the time of writing,
/// there are two implementors of [`ObjectIndexRelTo`]:
@ -469,9 +513,10 @@ fn diagnostic_node_missing_desc<O: ObjectKind>(
/// and uses the appropriate specialization.
///
/// Because of other trait bounds leading up to this point,
/// including those on [`Asg::add_edge`] and [`ObjectIndexRelTo`],
/// this cannot be invoked for any `to_oi` that is not a valid target
/// for `Self`.
/// including those on [`Asg::add_tree_edge`]/[`Asg::add_cross_edge`] and
/// [`ObjectIndexRelTo`],
/// this cannot be invoked for any `to_oi` that is not a valid target
/// for `Self`.
/// But we cannot be too strict on that bound _here_,
/// because otherwise it's not general enough for
/// [`ObjectIndexTo::pre_add_edge`].
@ -499,14 +544,15 @@ pub trait AsgRelMut<OB: ObjectRelatable>: ObjectRelatable {
/// since the [`ObjectIndex`] APIs may not be utilized
/// (e.g. in the case of [`ObjectIndexRelTo`].
///
/// This is invoked by [`Asg::add_edge`].
/// This is invoked by [`Asg::add_tree_edge`] and
/// [`Asg::add_cross_edge`].
/// The provided `commit` callback will complete the addition of the
/// edge if provided [`Ok`],
/// and the commit cannot fail.
/// If [`Err`] is provided to `commit`,
/// then [`Asg::add_edge`] will fail with that error.
/// then the calling [`Asg`] method will fail with that error.
///
/// Unlike the type of [`Asg::add_edge`],
/// Unlike the type of [`Asg::add_tree_edge`]/[`Asg::add_cross_edge`],
/// the source [`ObjectIndex`] has been narrowed to the appropriate
/// type for you.
fn pre_add_edge(
@ -537,11 +583,19 @@ impl<OA: ObjectRelatable, OB: ObjectRelatable> AsgRelMut<OB> for OA {
}
}
/// The relationship proposed by [`Asg::add_edge`],
/// requiring approval from [`AsgRelMut::pre_add_edge`].
/// The relationship proposed by [`Asg::add_tree_edge`] or
/// [`Asg::add_cross_edge`],
/// requiring approval from [`AsgRelMut::pre_add_edge`].
pub struct ProposedRel<OA: ObjectKind, OB: ObjectKind> {
from_oi: ObjectIndex<OA>,
to_oi: ObjectIndex<OB>,
/// Reference span.
///
/// This will be [`Some`] by [`Asg::add_cross_edge`],
/// but will always be [`None`] via [`Asg::add_tree_edge`].
/// This can therefore be used to determine whether an edge is a tree or
/// a cross edge.
ref_span: Option<Span>,
}

View File

@ -152,9 +152,10 @@ pub use ident::Ident;
pub use meta::Meta;
pub use pkg::Pkg;
pub use rel::{
DynObjectRel, ObjectIndexRelTo, ObjectIndexTo, ObjectIndexToTree,
ObjectIndexTreeRelTo, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy,
ObjectRelatable, ObjectTreeRelTo,
DynObjectRel, ObjectCrossRelTo, ObjectIndexCrossRelTo, ObjectIndexRelTo,
ObjectIndexTo, ObjectIndexToCross, ObjectIndexToTree, ObjectIndexTreeRelTo,
ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable,
ObjectTreeRelTo,
};
pub use root::Root;
pub use tpl::Tpl;
@ -163,9 +164,10 @@ pub use tpl::Tpl;
pub mod prelude {
pub use super::{
super::{super::error::AsgError, Asg, AsgRelMut},
Object, ObjectIndex, ObjectIndexRelTo, ObjectKind, ObjectRel,
ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable,
ObjectTreeRelTo,
Object, ObjectCrossRelTo, ObjectIndex, ObjectIndexCrossRelTo,
ObjectIndexRelTo, ObjectIndexToCross, ObjectIndexToTree,
ObjectIndexTreeRelTo, ObjectKind, ObjectRel, ObjectRelFrom,
ObjectRelTo, ObjectRelTy, ObjectRelatable, ObjectTreeRelTo,
};
}
@ -593,52 +595,81 @@ impl<O: ObjectKind> ObjectIndex<O> {
}
}
/// Add an edge from `self` to `to_oi` on the provided [`Asg`].
/// Add a tree edge from `self` to `to_oi` on the provided [`Asg`].
///
/// Since the only invariant asserted by [`ObjectIndexRelTo`] is that
/// it may be related to `OB`,
/// this method will only permit edges to `OB`;
/// nothing else about the inner object is statically known.
///
/// See also [`Self::add_edge_from`].
/// See also [`Self::add_tree_edge_from`].
///
/// _This method must remain private_,
/// forcing callers to go through APIs for specific operations that
/// allow objects to enforce their own invariants.
/// This is also the reason why this method is defined here rather than
/// on [`ObjectIndexRelTo`].
fn add_edge_to<OB: ObjectRelatable>(
fn add_tree_edge_to<OB: ObjectRelatable>(
self,
asg: &mut Asg,
to_oi: ObjectIndex<OB>,
ctx_span: Option<Span>,
) -> Result<Self, AsgError>
where
Self: ObjectIndexRelTo<OB>,
Self: ObjectIndexTreeRelTo<OB>,
{
asg.add_edge(self, to_oi, ctx_span).map(|()| self)
asg.add_tree_edge(self, to_oi).map(|()| self)
}
/// Add an edge from `from_oi` to `self` on the provided [`Asg`].
/// Add a tree edge from `from_oi` to `self` on the provided [`Asg`].
///
/// An edge can only be added if ontologically valid;
/// see [`ObjectRelTo`] for more information.
///
/// See also [`Self::add_edge_to`].
/// See also [`Self::add_tree_edge_to`].
///
/// _This method must remain private_,
/// forcing callers to go through APIs for specific operations that
/// allow objects to enforce their own invariants.
fn add_edge_from<OA: ObjectIndexRelTo<O>>(
fn add_tree_edge_from<OA: ObjectIndexTreeRelTo<O>>(
self,
asg: &mut Asg,
from_oi: OA,
ctx_span: Option<Span>,
) -> Result<Self, AsgError>
where
O: ObjectRelatable,
{
asg.add_edge(from_oi, self, ctx_span).map(|()| self)
asg.add_tree_edge(from_oi, self).map(|()| self)
}
/// Add a cross edge from `self` to `to_oi` on the provided [`Asg`].
///
/// For more information,
/// see [`Self::add_tree_edge_to`].
///
/// See also [`Self::add_cross_edge_from`].
fn add_cross_edge_to<OB: ObjectRelatable>(
self,
asg: &mut Asg,
to_oi: ObjectIndex<OB>,
ref_span: Span,
) -> Result<Self, AsgError>
where
Self: ObjectIndexCrossRelTo<OB>,
{
asg.add_cross_edge(self, to_oi, ref_span).map(|()| self)
}
/// Add a cross edge from `from_oi` to `self` on the provided [`Asg`].
///
/// For more information,
/// see [`Self::add_tree_edge_from`].
///
/// See also [`Self::add_cross_edge_to`].
fn add_cross_edge_from<OA: ObjectIndexCrossRelTo<O>>(
self,
asg: &mut Asg,
from_oi: OA,
ref_span: Span,
) -> Result<Self, AsgError>
where
O: ObjectRelatable,
{
asg.add_cross_edge(from_oi, self, ref_span).map(|()| self)
}
/// Create an iterator over the [`ObjectIndex`]es of the outgoing edges
@ -813,7 +844,24 @@ impl<O: ObjectKind> ObjectIndex<O> {
.filter(|_| O::rel_ty() == OB::rel_ty())
}
/// Root this object in the ASG's [`Root`] object.
/// Root this object in the ASG's [`Root`] object,
/// acting as an owner of the object.
///
/// This is intended for rooting toplevel objects that otherwise have no
/// owner for a given compilation unit,
/// allowing them to be reachable from the graph root.
///
/// See also [`Self::root_cross`].
pub fn root_tree(self, asg: &mut Asg) -> Result<Self, AsgError>
where
Root: ObjectTreeRelTo<O>,
{
asg.root(self.span())
.add_tree_edge_to(asg, self)
.map(|_| self)
}
/// Root a reference to this object in the ASG's [`Root`] object.
///
/// A rooted object is forced to be reachable.
/// This should only be utilized when necessary for toplevel objects;
@ -821,18 +869,21 @@ impl<O: ObjectKind> ObjectIndex<O> {
/// objects.
/// Forcing objects to be reachable can prevent them from being
/// optimized away if they are not used.
pub fn root(self, asg: &mut Asg) -> Result<Self, AsgError>
///
/// See also [`Self::root_tree`].
pub fn root_cross(self, asg: &mut Asg) -> Result<Self, AsgError>
where
Root: ObjectRelTo<O>,
Root: ObjectCrossRelTo<O>,
{
asg.root(self.span())
.add_edge_to(asg, self, None)
.add_cross_edge_to(asg, self, self.span())
.map(|_| self)
}
/// Whether this object has been rooted in the ASG's [`Root`] object.
///
/// See [`Self::root`] for more information.
/// See [`Self::root_tree`] and [`Self::root_cross`] for more
/// information.
pub fn is_rooted(&self, asg: &Asg) -> bool
where
Root: ObjectRelTo<O>,
@ -910,7 +961,7 @@ impl<O: ObjectKind> ObjectIndex<O> {
oi: ObjectIndex<Ident>,
) -> Result<Self, AsgError>
where
Self: ObjectIndexRelTo<Ident>,
Self: ObjectIndexTreeRelTo<Ident>,
{
oi.defined_by(asg, self).map(|_| self)
}
@ -927,10 +978,10 @@ impl<O: ObjectKind> ObjectIndex<O> {
clause: SPair,
) -> Result<Self, AsgError>
where
O: ObjectRelTo<Doc>,
O: ObjectTreeRelTo<Doc>,
{
let oi_doc = asg.create(Doc::new_indep_clause(clause));
self.add_edge_to(asg, oi_doc, None)
self.add_tree_edge_to(asg, oi_doc)
}
/// Retrieve a description of this expression using a short independent

View File

@ -19,7 +19,7 @@
//! Expressions on the ASG.
use super::{prelude::*, Doc, Ident, ObjectIndexTo, Tpl};
use super::{prelude::*, Doc, Ident, ObjectIndexToTree, Tpl};
use crate::{f::Map, num::Dim, span::Span};
use std::fmt::Display;
@ -235,7 +235,7 @@ impl ObjectIndex<Expr> {
expr: Expr,
) -> Result<ObjectIndex<Expr>, AsgError> {
let oi_subexpr = asg.create(expr);
oi_subexpr.add_edge_from(asg, self, None)
oi_subexpr.add_tree_edge_from(asg, self)
}
/// Reference the value of the expression identified by `oi_ident` as if
@ -245,7 +245,7 @@ impl ObjectIndex<Expr> {
asg: &mut Asg,
oi_ident: ObjectIndex<Ident>,
) -> Result<Self, AsgError> {
self.add_edge_to(asg, oi_ident, Some(oi_ident.span()))
self.add_cross_edge_to(asg, oi_ident, oi_ident.span())
}
/// The expression is held by the container `oi_container`.
@ -263,8 +263,8 @@ impl ObjectIndex<Expr> {
pub fn held_by(
&self,
asg: &mut Asg,
oi_container: ObjectIndexTo<Expr>,
oi_container: ObjectIndexToTree<Expr>,
) -> Result<Self, AsgError> {
self.add_edge_from(asg, oi_container, None)
self.add_tree_edge_from(asg, oi_container)
}
}

View File

@ -1221,7 +1221,7 @@ impl ObjectIndex<Ident> {
self.try_map_obj(asg, |obj| obj.resolve(name.span(), kind, src))
.map_err(Into::into)
.map(|ident| {
is_auto_root.then(|| self.root(asg));
is_auto_root.then(|| self.root_cross(asg));
ident
})
}
@ -1257,7 +1257,7 @@ impl ObjectIndex<Ident> {
definition: ObjectIndex<O>,
) -> Result<ObjectIndex<Ident>, AsgError>
where
Ident: ObjectRelTo<O>,
Ident: ObjectTreeRelTo<O>,
ObjectIndex<O>: Into<IdentDefinition>,
{
let my_span = self.into();
@ -1314,7 +1314,7 @@ impl ObjectIndex<Ident> {
// and use the newly provided `id` and its span.
Missing(_) => Ok(Transparent(id)),
})
.and_then(|ident_oi| ident_oi.add_edge_to(asg, definition, None))
.and_then(|ident_oi| ident_oi.add_tree_edge_to(asg, definition))
}
/// Set the fragment associated with a concrete identifier.
@ -1414,9 +1414,9 @@ impl ObjectIndex<Ident> {
pub fn defined_by(
&self,
asg: &mut Asg,
oi_root: impl ObjectIndexRelTo<Ident>,
oi_root: impl ObjectIndexTreeRelTo<Ident>,
) -> Result<Self, AsgError> {
self.add_edge_from(asg, oi_root, None)
self.add_tree_edge_from(asg, oi_root)
}
/// Declare that `oi_dep` is an opaque dependency of `self`.
@ -1425,7 +1425,7 @@ impl ObjectIndex<Ident> {
asg: &mut Asg,
oi_dep: ObjectIndex<Ident>,
) -> Result<Self, AsgError> {
self.add_edge_to(asg, oi_dep, None)
self.add_tree_edge_to(asg, oi_dep)
}
/// Retrieve either the concrete name of the identifier or the name of
@ -1491,7 +1491,7 @@ impl ObjectIndex<Ident> {
at: Span,
) -> Result<ObjectIndex<Ident>, AsgError> {
asg.create(Ident::new_abstract(at))
.add_edge_to(asg, self, Some(at))
.add_cross_edge_to(asg, self, at)
}
}

View File

@ -243,7 +243,7 @@ impl ObjectIndex<Meta> {
for rel_lexeme in rels {
let oi = asg.create(Meta::Lexeme(rel_lexeme.span(), rel_lexeme));
self.add_edge_to(asg, oi, None)?;
self.add_tree_edge_to(asg, oi)?;
}
Ok(self)
@ -297,12 +297,12 @@ impl ObjectIndex<Meta> {
// we must add the edge before appending the ref since
// concatenation will occur during expansion in edge order.
if let Some(orig) = pre {
asg.create(orig).add_edge_from(asg, self, None)?;
asg.create(orig).add_tree_edge_from(asg, self)?;
}
// Having been guaranteed a `ConcatList` above,
// we now only need to append an edge that references what to
// concatenate.
self.add_edge_to(asg, oi_ref, Some(oi_ref.span()))
self.add_cross_edge_to(asg, oi_ref, oi_ref.span())
}
}

View File

@ -134,7 +134,7 @@ impl ObjectIndex<Pkg> {
let parent = self.resolve(asg);
let oi_import = asg.create(Pkg::new_imported(parent, namespec)?);
self.add_edge_to(asg, oi_import, Some(namespec.span()))
self.add_cross_edge_to(asg, oi_import, namespec.span())
}
/// Arbitrary text serving as documentation in a literate style.
@ -144,6 +144,6 @@ impl ObjectIndex<Pkg> {
text: SPair,
) -> Result<Self, AsgError> {
let oi_doc = asg.create(Doc::new_text(text));
self.add_edge_to(asg, oi_doc, None)
self.add_tree_edge_to(asg, oi_doc)
}
}

View File

@ -203,14 +203,17 @@ macro_rules! object_rel {
// Similar to above but providing _static_ information to the type
// system.
// The above could be rolled into this at some point.
(@impl_rel_to $from:ident cross $kind:ident) => {};
(@impl_rel_to $from:ident cross $kind:ident) => {
impl ObjectCrossRelTo<$kind> for $from {}
};
(@impl_rel_to $from:ident tree $kind:ident) => {
impl ObjectTreeRelTo<$kind> for $from {}
};
(@impl_rel_to $from:ident dyn $kind:ident) => {
// It _could_ be a tree edge;
// It _could_ be either a tree or cross edge;
// we can't know statically.
impl ObjectTreeRelTo<$kind> for $from {}
impl ObjectCrossRelTo<$kind> for $from {}
};
}
@ -536,11 +539,31 @@ pub trait ObjectRelFrom<OA: ObjectKind + ObjectRelatable> =
/// see that trait for more information.
/// This trait is intended to be used in contexts where the distinction
/// between reference and ownership is important.
///
/// For cross edges,
/// see [`ObjectCrossRelTo`];
/// dynamic edges will implement both this trait and that one.
pub trait ObjectTreeRelTo<OB: ObjectKind + ObjectRelatable>:
ObjectRelTo<OB>
{
}
/// Indicate that an [`ObjectKind`] `Self` _could possibly be a reference
/// to_ an [`ObjectKind`] `OB`.
///
/// This is a stronger assertion than [`ObjectRelTo`];
/// see that trait for more information.
/// This trait is intended to be used in contexts where the distinction
/// between reference and ownership is important.
///
/// For tree edges,
/// see [`ObjectTreeRelTo`];
/// dynamic edges will implement both this trait and that one.
pub trait ObjectCrossRelTo<OB: ObjectKind + ObjectRelatable>:
ObjectRelTo<OB>
{
}
/// Identify [`Self::Rel`] as a sum type consisting of the subset of
/// [`Object`] variants representing the valid _target_ edges of
/// [`Self`].
@ -1036,6 +1059,32 @@ impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexToTree<OB> {
}
}
impl<OB: ObjectRelatable> ObjectIndexRelTo<OB> for ObjectIndexToCross<OB> {
fn src_rel_ty(&self) -> ObjectRelTy {
match self {
Self(oito) => oito.src_rel_ty(),
}
}
fn widen(&self) -> ObjectIndex<Object> {
match self {
Self(oito) => oito.widen(),
}
}
fn pre_add_edge(
&self,
asg: &mut Asg,
to_oi: ObjectIndex<OB>,
ref_span: Option<Span>,
commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError> {
match self {
Self(oito) => oito.pre_add_edge(asg, to_oi, ref_span, commit),
}
}
}
impl<OB: ObjectRelatable> From<ObjectIndexTo<OB>> for ObjectIndex<Object> {
fn from(oi_rel: ObjectIndexTo<OB>) -> Self {
oi_rel.widen()
@ -1072,7 +1121,29 @@ where
{
}
pub use private::{ObjectIndexTo, ObjectIndexToTree};
/// An [`ObjectIndex`]-like object that is able to create a _cross_ edge to
/// [`ObjectKind`] `OB`.
///
/// This allows for generic graph operations that operate on ownership
/// relationships without having to know the type of the source
/// object (`Self`).
///
/// This is a specialization of [`ObjectIndexRelTo`].
pub trait ObjectIndexCrossRelTo<OB: ObjectRelatable>:
ObjectIndexRelTo<OB> + Into<ObjectIndexToCross<OB>>
{
}
impl<OB: ObjectRelatable> ObjectIndexCrossRelTo<OB> for ObjectIndexToCross<OB> {}
impl<O: ObjectRelatable, OB: ObjectRelatable> ObjectIndexCrossRelTo<OB>
for ObjectIndex<O>
where
O: ObjectCrossRelTo<OB>,
{
}
pub use private::{ObjectIndexTo, ObjectIndexToCross, ObjectIndexToTree};
/// Private inner module to ensure that nothing is able to bypass invariants
/// by constructing [`ObjectIndexTo`] manually.
@ -1204,6 +1275,14 @@ mod private {
}
}
impl<OB: ObjectRelatable> From<ObjectIndexToCross<OB>> for ObjectIndexTo<OB> {
fn from(value: ObjectIndexToCross<OB>) -> Self {
match value {
ObjectIndexToCross(oit) => oit,
}
}
}
/// Some [`ObjectIndex`] that can create a _tree_ edge to `OB`.
///
/// This is a specialization of
@ -1260,4 +1339,61 @@ mod private {
}
}
}
/// Some [`ObjectIndex`] that can create a _cross_ edge to `OB`.
///
/// This is a specialization of
/// (and contains)
/// [`ObjectIndexTo`];
/// see that for more information.
///
/// See also [`ObjectIndexTreeRelTo`].
#[derive(Debug)]
pub struct ObjectIndexToCross<OB: ObjectRelatable>(ObjectIndexTo<OB>);
impl<OB: ObjectRelatable, O: ObjectRelatable> From<ObjectIndex<O>>
for ObjectIndexToCross<OB>
where
O: ObjectCrossRelTo<OB>,
{
fn from(oi: ObjectIndex<O>) -> Self {
Self(oi.into())
}
}
// Deriving any of the below were introducing trait bounds on `OB`.
impl<OB: ObjectRelatable> PartialEq for ObjectIndexToCross<OB> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self(oi), Self(oi_other)) => oi == oi_other,
}
}
}
impl<OB: ObjectRelatable> Eq for ObjectIndexToCross<OB> {}
impl<OB: ObjectRelatable> Hash for ObjectIndexToCross<OB> {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self(oi) => oi.hash(state),
}
}
}
impl<OB: ObjectRelatable> Clone for ObjectIndexToCross<OB> {
fn clone(&self) -> Self {
Self(self.0)
}
}
impl<OB: ObjectRelatable> Copy for ObjectIndexToCross<OB> {}
impl<OB: ObjectRelatable> From<ObjectIndexToCross<OB>> for Span {
fn from(oi: ObjectIndexToCross<OB>) -> Self {
match oi {
ObjectIndexToCross(inner) => inner.span(),
}
}
}
}

View File

@ -67,6 +67,7 @@ impl ObjectIndex<Root> {
asg: &mut Asg,
oi: ObjectIndex<Ident>,
) -> Result<ObjectIndex<Ident>, AsgError> {
oi.add_edge_from(asg, *self, None)
// TODO: Is this a valid span?
oi.add_cross_edge_from(asg, *self, oi.span())
}
}

View File

@ -232,6 +232,10 @@ impl Display for TplShape {
}
}
// TODO: Inlining code into this otherwise-declarative abstraction has
// tainted it.
// This can be refactored into a better abstraction over time as
// requirements are explored though implementation.
object_rel! {
/// Templates may expand into nearly any context,
/// and must therefore be able to contain just about anything.
@ -303,8 +307,6 @@ object_rel! {
// be hoisted into the rooting context of the
// application site,
// which does not impact template shape.
// TODO: Let's make that span assumption explicit in the
// `ProposeRel` abstraction.
(None, _) => Ok(()),
}?;
@ -393,7 +395,7 @@ impl ObjectIndex<Tpl> {
oi_apply: ObjectIndex<Ident>,
ref_span: Span,
) -> Result<Self, AsgError> {
self.add_edge_to(asg, oi_apply, Some(ref_span))
self.add_cross_edge_to(asg, oi_apply, ref_span)
}
/// Directly reference this template from another object
@ -406,12 +408,12 @@ impl ObjectIndex<Tpl> {
/// template.
/// If this template is _not_ closed,
/// it will result in an error during evaluation.
pub fn expand_into<OP: ObjectIndexRelTo<Tpl>>(
pub fn expand_into<OP: ObjectIndexTreeRelTo<Tpl>>(
self,
asg: &mut Asg,
oi_target_parent: OP,
) -> Result<Self, AsgError> {
self.add_edge_from(asg, oi_target_parent, None)
self.add_tree_edge_from(asg, oi_target_parent)
}
/// Arbitrary text serving as documentation in a literate style,
@ -422,6 +424,6 @@ impl ObjectIndex<Tpl> {
text: SPair,
) -> Result<Self, AsgError> {
let oi_doc = asg.create(Doc::new_text(text));
self.add_edge_to(asg, oi_doc, None)
self.add_tree_edge_to(asg, oi_doc)
}
}