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

View File

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

View File

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

View File

@ -22,8 +22,8 @@
//! ![Visualization of ASG ontology](../ontviz.svg) //! ![Visualization of ASG ontology](../ontviz.svg)
use self::object::{ use self::object::{
DynObjectRel, ObjectIndexRelTo, ObjectRelFrom, ObjectRelTy, DynObjectRel, ObjectIndexCrossRelTo, ObjectIndexRelTo,
ObjectRelatable, Root, ObjectIndexTreeRelTo, ObjectRelFrom, ObjectRelTy, ObjectRelatable, Root,
}; };
use super::{AsgError, Object, ObjectIndex, ObjectKind}; use super::{AsgError, Object, ObjectIndex, ObjectKind};
@ -190,32 +190,74 @@ impl Asg {
ObjectIndex::new(node_id, span) 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`. /// [`ObjectIndex`] `from_oi` to the object represented by `to_oi`.
/// ///
/// The edge may optionally contain a _contextual [`Span`]_, /// A tree edge represents _ownership_ over the target object
/// in cases where it is important to distinguish between the span /// (`from_oi` owns `to_oi`),
/// associated with the target and the span associated with the /// forming a tree parent/child relationships between objects.
/// _reference_ to the target. /// 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, /// 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 /// Callers external to this module should use [`ObjectIndex`] APIs to
/// manipulate the graph; /// manipulate the graph;
/// this allows those objects to uphold their own invariants /// this allows those objects to uphold their own invariants
/// relative to the state of the graph. /// 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, &mut self,
from_oi: OA, from_oi: OA,
to_oi: ObjectIndex<OB>, to_oi: ObjectIndex<OB>,
ref_span: Option<Span>,
) -> Result<(), AsgError> { ) -> 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( asg.graph.add_edge(
from_oi.widen().into(), from_oi.widen().into(),
to_oi.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? /// How Does This Work With Trait Specialization?
/// ============================================= /// =============================================
/// [`Asg::add_edge`] is provided a [`ObjectIndexRelTo`], /// [`Asg::add_tree_edge`]/[`Asg::add_cross_edge`] is provided a
/// which needs narrowing to an appropriate source [`ObjectKind`] so that /// [`ObjectIndexRelTo`],
/// we can invoke [`<O as AsgRelMut>::pre_add_edge`](AsgRelMut::pre_add_edge). /// 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, /// At the time of writing,
/// there are two implementors of [`ObjectIndexRelTo`]: /// there are two implementors of [`ObjectIndexRelTo`]:
@ -469,7 +513,8 @@ fn diagnostic_node_missing_desc<O: ObjectKind>(
/// and uses the appropriate specialization. /// and uses the appropriate specialization.
/// ///
/// Because of other trait bounds leading up to this point, /// Because of other trait bounds leading up to this point,
/// including those on [`Asg::add_edge`] and [`ObjectIndexRelTo`], /// 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 /// this cannot be invoked for any `to_oi` that is not a valid target
/// for `Self`. /// for `Self`.
/// But we cannot be too strict on that bound _here_, /// But we cannot be too strict on that bound _here_,
@ -499,14 +544,15 @@ pub trait AsgRelMut<OB: ObjectRelatable>: ObjectRelatable {
/// since the [`ObjectIndex`] APIs may not be utilized /// since the [`ObjectIndex`] APIs may not be utilized
/// (e.g. in the case of [`ObjectIndexRelTo`]. /// (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 /// The provided `commit` callback will complete the addition of the
/// edge if provided [`Ok`], /// edge if provided [`Ok`],
/// and the commit cannot fail. /// and the commit cannot fail.
/// If [`Err`] is provided to `commit`, /// 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 /// the source [`ObjectIndex`] has been narrowed to the appropriate
/// type for you. /// type for you.
fn pre_add_edge( fn pre_add_edge(
@ -537,11 +583,19 @@ impl<OA: ObjectRelatable, OB: ObjectRelatable> AsgRelMut<OB> for OA {
} }
} }
/// The relationship proposed by [`Asg::add_edge`], /// The relationship proposed by [`Asg::add_tree_edge`] or
/// [`Asg::add_cross_edge`],
/// requiring approval from [`AsgRelMut::pre_add_edge`]. /// requiring approval from [`AsgRelMut::pre_add_edge`].
pub struct ProposedRel<OA: ObjectKind, OB: ObjectKind> { pub struct ProposedRel<OA: ObjectKind, OB: ObjectKind> {
from_oi: ObjectIndex<OA>, from_oi: ObjectIndex<OA>,
to_oi: ObjectIndex<OB>, 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>, ref_span: Option<Span>,
} }

View File

@ -152,9 +152,10 @@ pub use ident::Ident;
pub use meta::Meta; pub use meta::Meta;
pub use pkg::Pkg; pub use pkg::Pkg;
pub use rel::{ pub use rel::{
DynObjectRel, ObjectIndexRelTo, ObjectIndexTo, ObjectIndexToTree, DynObjectRel, ObjectCrossRelTo, ObjectIndexCrossRelTo, ObjectIndexRelTo,
ObjectIndexTreeRelTo, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectIndexTo, ObjectIndexToCross, ObjectIndexToTree, ObjectIndexTreeRelTo,
ObjectRelatable, ObjectTreeRelTo, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable,
ObjectTreeRelTo,
}; };
pub use root::Root; pub use root::Root;
pub use tpl::Tpl; pub use tpl::Tpl;
@ -163,9 +164,10 @@ pub use tpl::Tpl;
pub mod prelude { pub mod prelude {
pub use super::{ pub use super::{
super::{super::error::AsgError, Asg, AsgRelMut}, super::{super::error::AsgError, Asg, AsgRelMut},
Object, ObjectIndex, ObjectIndexRelTo, ObjectKind, ObjectRel, Object, ObjectCrossRelTo, ObjectIndex, ObjectIndexCrossRelTo,
ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable, ObjectIndexRelTo, ObjectIndexToCross, ObjectIndexToTree,
ObjectTreeRelTo, 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 /// See also [`Self::add_tree_edge_from`].
/// 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`].
/// ///
/// _This method must remain private_, /// _This method must remain private_,
/// forcing callers to go through APIs for specific operations that /// forcing callers to go through APIs for specific operations that
/// allow objects to enforce their own invariants. /// allow objects to enforce their own invariants.
/// This is also the reason why this method is defined here rather than /// This is also the reason why this method is defined here rather than
/// on [`ObjectIndexRelTo`]. /// on [`ObjectIndexRelTo`].
fn add_edge_to<OB: ObjectRelatable>( fn add_tree_edge_to<OB: ObjectRelatable>(
self, self,
asg: &mut Asg, asg: &mut Asg,
to_oi: ObjectIndex<OB>, to_oi: ObjectIndex<OB>,
ctx_span: Option<Span>,
) -> Result<Self, AsgError> ) -> Result<Self, AsgError>
where 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; /// An edge can only be added if ontologically valid;
/// see [`ObjectRelTo`] for more information. /// see [`ObjectRelTo`] for more information.
/// ///
/// See also [`Self::add_edge_to`]. /// See also [`Self::add_tree_edge_to`].
/// ///
/// _This method must remain private_, /// _This method must remain private_,
/// forcing callers to go through APIs for specific operations that /// forcing callers to go through APIs for specific operations that
/// allow objects to enforce their own invariants. /// allow objects to enforce their own invariants.
fn add_edge_from<OA: ObjectIndexRelTo<O>>( fn add_tree_edge_from<OA: ObjectIndexTreeRelTo<O>>(
self, self,
asg: &mut Asg, asg: &mut Asg,
from_oi: OA, from_oi: OA,
ctx_span: Option<Span>,
) -> Result<Self, AsgError> ) -> Result<Self, AsgError>
where where
O: ObjectRelatable, 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 /// 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()) .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. /// A rooted object is forced to be reachable.
/// This should only be utilized when necessary for toplevel objects; /// This should only be utilized when necessary for toplevel objects;
@ -821,18 +869,21 @@ impl<O: ObjectKind> ObjectIndex<O> {
/// objects. /// objects.
/// Forcing objects to be reachable can prevent them from being /// Forcing objects to be reachable can prevent them from being
/// optimized away if they are not used. /// 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 where
Root: ObjectRelTo<O>, Root: ObjectCrossRelTo<O>,
{ {
asg.root(self.span()) asg.root(self.span())
.add_edge_to(asg, self, None) .add_cross_edge_to(asg, self, self.span())
.map(|_| self) .map(|_| self)
} }
/// Whether this object has been rooted in the ASG's [`Root`] object. /// 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 pub fn is_rooted(&self, asg: &Asg) -> bool
where where
Root: ObjectRelTo<O>, Root: ObjectRelTo<O>,
@ -910,7 +961,7 @@ impl<O: ObjectKind> ObjectIndex<O> {
oi: ObjectIndex<Ident>, oi: ObjectIndex<Ident>,
) -> Result<Self, AsgError> ) -> Result<Self, AsgError>
where where
Self: ObjectIndexRelTo<Ident>, Self: ObjectIndexTreeRelTo<Ident>,
{ {
oi.defined_by(asg, self).map(|_| self) oi.defined_by(asg, self).map(|_| self)
} }
@ -927,10 +978,10 @@ impl<O: ObjectKind> ObjectIndex<O> {
clause: SPair, clause: SPair,
) -> Result<Self, AsgError> ) -> Result<Self, AsgError>
where where
O: ObjectRelTo<Doc>, O: ObjectTreeRelTo<Doc>,
{ {
let oi_doc = asg.create(Doc::new_indep_clause(clause)); 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 /// Retrieve a description of this expression using a short independent

View File

@ -19,7 +19,7 @@
//! Expressions on the ASG. //! 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 crate::{f::Map, num::Dim, span::Span};
use std::fmt::Display; use std::fmt::Display;
@ -235,7 +235,7 @@ impl ObjectIndex<Expr> {
expr: Expr, expr: Expr,
) -> Result<ObjectIndex<Expr>, AsgError> { ) -> Result<ObjectIndex<Expr>, AsgError> {
let oi_subexpr = asg.create(expr); 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 /// Reference the value of the expression identified by `oi_ident` as if
@ -245,7 +245,7 @@ impl ObjectIndex<Expr> {
asg: &mut Asg, asg: &mut Asg,
oi_ident: ObjectIndex<Ident>, oi_ident: ObjectIndex<Ident>,
) -> Result<Self, AsgError> { ) -> 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`. /// The expression is held by the container `oi_container`.
@ -263,8 +263,8 @@ impl ObjectIndex<Expr> {
pub fn held_by( pub fn held_by(
&self, &self,
asg: &mut Asg, asg: &mut Asg,
oi_container: ObjectIndexTo<Expr>, oi_container: ObjectIndexToTree<Expr>,
) -> Result<Self, AsgError> { ) -> 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)) self.try_map_obj(asg, |obj| obj.resolve(name.span(), kind, src))
.map_err(Into::into) .map_err(Into::into)
.map(|ident| { .map(|ident| {
is_auto_root.then(|| self.root(asg)); is_auto_root.then(|| self.root_cross(asg));
ident ident
}) })
} }
@ -1257,7 +1257,7 @@ impl ObjectIndex<Ident> {
definition: ObjectIndex<O>, definition: ObjectIndex<O>,
) -> Result<ObjectIndex<Ident>, AsgError> ) -> Result<ObjectIndex<Ident>, AsgError>
where where
Ident: ObjectRelTo<O>, Ident: ObjectTreeRelTo<O>,
ObjectIndex<O>: Into<IdentDefinition>, ObjectIndex<O>: Into<IdentDefinition>,
{ {
let my_span = self.into(); let my_span = self.into();
@ -1314,7 +1314,7 @@ impl ObjectIndex<Ident> {
// and use the newly provided `id` and its span. // and use the newly provided `id` and its span.
Missing(_) => Ok(Transparent(id)), 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. /// Set the fragment associated with a concrete identifier.
@ -1414,9 +1414,9 @@ impl ObjectIndex<Ident> {
pub fn defined_by( pub fn defined_by(
&self, &self,
asg: &mut Asg, asg: &mut Asg,
oi_root: impl ObjectIndexRelTo<Ident>, oi_root: impl ObjectIndexTreeRelTo<Ident>,
) -> Result<Self, AsgError> { ) -> 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`. /// Declare that `oi_dep` is an opaque dependency of `self`.
@ -1425,7 +1425,7 @@ impl ObjectIndex<Ident> {
asg: &mut Asg, asg: &mut Asg,
oi_dep: ObjectIndex<Ident>, oi_dep: ObjectIndex<Ident>,
) -> Result<Self, AsgError> { ) -> 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 /// Retrieve either the concrete name of the identifier or the name of
@ -1491,7 +1491,7 @@ impl ObjectIndex<Ident> {
at: Span, at: Span,
) -> Result<ObjectIndex<Ident>, AsgError> { ) -> Result<ObjectIndex<Ident>, AsgError> {
asg.create(Ident::new_abstract(at)) 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 { for rel_lexeme in rels {
let oi = asg.create(Meta::Lexeme(rel_lexeme.span(), rel_lexeme)); 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) Ok(self)
@ -297,12 +297,12 @@ impl ObjectIndex<Meta> {
// we must add the edge before appending the ref since // we must add the edge before appending the ref since
// concatenation will occur during expansion in edge order. // concatenation will occur during expansion in edge order.
if let Some(orig) = pre { 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, // Having been guaranteed a `ConcatList` above,
// we now only need to append an edge that references what to // we now only need to append an edge that references what to
// concatenate. // 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 parent = self.resolve(asg);
let oi_import = asg.create(Pkg::new_imported(parent, namespec)?); 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. /// Arbitrary text serving as documentation in a literate style.
@ -144,6 +144,6 @@ impl ObjectIndex<Pkg> {
text: SPair, text: SPair,
) -> Result<Self, AsgError> { ) -> Result<Self, AsgError> {
let oi_doc = asg.create(Doc::new_text(text)); 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 // Similar to above but providing _static_ information to the type
// system. // system.
// The above could be rolled into this at some point. // 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_rel_to $from:ident tree $kind:ident) => {
impl ObjectTreeRelTo<$kind> for $from {} impl ObjectTreeRelTo<$kind> for $from {}
}; };
(@impl_rel_to $from:ident dyn $kind:ident) => { (@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. // we can't know statically.
impl ObjectTreeRelTo<$kind> for $from {} 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. /// see that trait for more information.
/// This trait is intended to be used in contexts where the distinction /// This trait is intended to be used in contexts where the distinction
/// between reference and ownership is important. /// 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>: pub trait ObjectTreeRelTo<OB: ObjectKind + ObjectRelatable>:
ObjectRelTo<OB> 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 /// Identify [`Self::Rel`] as a sum type consisting of the subset of
/// [`Object`] variants representing the valid _target_ edges of /// [`Object`] variants representing the valid _target_ edges of
/// [`Self`]. /// [`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> { impl<OB: ObjectRelatable> From<ObjectIndexTo<OB>> for ObjectIndex<Object> {
fn from(oi_rel: ObjectIndexTo<OB>) -> Self { fn from(oi_rel: ObjectIndexTo<OB>) -> Self {
oi_rel.widen() 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 /// Private inner module to ensure that nothing is able to bypass invariants
/// by constructing [`ObjectIndexTo`] manually. /// 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`. /// Some [`ObjectIndex`] that can create a _tree_ edge to `OB`.
/// ///
/// This is a specialization of /// 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, asg: &mut Asg,
oi: ObjectIndex<Ident>, oi: ObjectIndex<Ident>,
) -> Result<ObjectIndex<Ident>, AsgError> { ) -> 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! { object_rel! {
/// Templates may expand into nearly any context, /// Templates may expand into nearly any context,
/// and must therefore be able to contain just about anything. /// and must therefore be able to contain just about anything.
@ -303,8 +307,6 @@ object_rel! {
// be hoisted into the rooting context of the // be hoisted into the rooting context of the
// application site, // application site,
// which does not impact template shape. // which does not impact template shape.
// TODO: Let's make that span assumption explicit in the
// `ProposeRel` abstraction.
(None, _) => Ok(()), (None, _) => Ok(()),
}?; }?;
@ -393,7 +395,7 @@ impl ObjectIndex<Tpl> {
oi_apply: ObjectIndex<Ident>, oi_apply: ObjectIndex<Ident>,
ref_span: Span, ref_span: Span,
) -> Result<Self, AsgError> { ) -> 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 /// Directly reference this template from another object
@ -406,12 +408,12 @@ impl ObjectIndex<Tpl> {
/// template. /// template.
/// If this template is _not_ closed, /// If this template is _not_ closed,
/// it will result in an error during evaluation. /// it will result in an error during evaluation.
pub fn expand_into<OP: ObjectIndexRelTo<Tpl>>( pub fn expand_into<OP: ObjectIndexTreeRelTo<Tpl>>(
self, self,
asg: &mut Asg, asg: &mut Asg,
oi_target_parent: OP, oi_target_parent: OP,
) -> Result<Self, AsgError> { ) -> 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, /// Arbitrary text serving as documentation in a literate style,
@ -422,6 +424,6 @@ impl ObjectIndex<Tpl> {
text: SPair, text: SPair,
) -> Result<Self, AsgError> { ) -> Result<Self, AsgError> {
let oi_doc = asg.create(Doc::new_text(text)); 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)
} }
} }