tamer: asg::graph: Work AsgRelMut specialization into `object_rel!` macro

This formalizes the previous commit a bit more and adds documentation
explaining why it exists and how it works.  Look there for more
information.

This has been a lot of setup work.  Hopefully things are now easier in the
future.  And now we have nice declarative type-level hooks into the graph!

DEV-13163
main
Mike Gerwitz 2023-07-25 15:35:57 -04:00
parent 37c962a7ee
commit aec721f4fa
9 changed files with 123 additions and 52 deletions

View File

@ -40,6 +40,9 @@ use petgraph::{
};
use std::{fmt::Debug, result::Result};
#[cfg(doc)]
use object::{ObjectIndexTo, Tpl};
pub mod object;
pub mod visit;
pub mod xmli;
@ -403,7 +406,79 @@ fn diagnostic_node_missing_desc<O: ObjectKind>(
/// trait,
/// but the current module structure together with Rust's visibility
/// with sibling modules doesn't seem to make that possible.
pub trait AsgRelMut<OB: ObjectRelatable>: 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).
///
/// At the time of writing,
/// there are two implementors of [`ObjectIndexRelTo`]:
///
/// - [`ObjectIndex<O>`],
/// for which we will know `O: ObjectKind`.
/// - [`ObjectIndexTo<OB>`],
/// for which we only know the _target_ `OB: ObjectKind`.
///
/// The entire purpose of [`ObjectIndexTo`] is to allow for a dynamic
/// source [`ObjectKind`];
/// we do not know what it is statically.
/// So [`ObjectIndexTo::pre_add_edge`] is a method that will dynamically
/// branch to an appropriate static path to invoke the correct
/// [`AsgRelMut::pre_add_edge`].
///
/// And there's the problem.
///
/// We match on each [`ObjectRelTy`] based on
/// [`ObjectIndexTo::src_rel_ty`],
/// and invoke the appropriate [`AsgRelMut::pre_add_edge`].
/// But the trait bound on `OB` for the `ObjectIndexRelTo` `impl` is
/// [`ObjectRelatable`].
/// So it resolves as `AsgRelMut<OB: ObjectRelatable>`.
///
/// But we don't have that implementation.
/// We have implementations for _individual target [`ObjectRelatable`]s,
/// e.g. `impl AsgRelMut<Expr> for Tpl`.
/// So Rust rightfully complains that `AsgRelMut<OB: ObjectRelatable>`
/// is not implemented for [`Tpl`].
/// (Go ahead and remove the generic `impl` block containing `default fn`
/// and see what happens.)
///
/// Of course,
/// _we_ know that there's a trait implemented for every possible
/// [`ObjectRelFrom<Tpl>`],
/// because `object_rel!` does that for us based on the same
/// definition that generates those other types.
/// But Rust does not perform that type of analysis---
/// it does not know that we've accounted for every type.
/// So the `default fn`` uses the unstable `min_specialization` feature to
/// satisfy those more generic trait bounds,
/// making the compiler happy.
///
/// But if Rust is seeing `OB: ObjectRelatable`,
/// why is it not monomorphizing to _this_ one rather than the more
/// specialized implementation?
///
/// That type error described above is contemplating bounds for _any
/// potential caller_.
/// But when we're about to add an edge,
/// we're invoking with a specific type of `OB`.
/// Monomorphization takes place at that point,
/// with the expected type,
/// 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`.
/// But we cannot be too strict on that bound _here_,
/// because otherwise it's not general enough for
/// [`ObjectIndexTo::pre_add_edge`].
/// We could do more runtime verification and further refine types,
/// but that is a lot of work for no additional practical benefit,
/// at least at this time.
pub trait AsgRelMut<OB: ObjectRelatable>: ObjectRelatable {
/// Allow an object to handle or reject the creation of an edge from it
/// to another object.
///
@ -440,6 +515,27 @@ pub trait AsgRelMut<OB: ObjectRelatable>: ObjectKind {
_to_oi: ObjectIndex<OB>,
_ctx_span: Option<Span>,
commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError>;
}
impl<O: ObjectRelatable, OB: ObjectRelatable> AsgRelMut<OB> for O {
/// Default edge creation method for all [`ObjectKind`]s.
///
/// This takes the place of a default implementation on the trait itself
/// above.
/// It will be invoked any time there is not a more specialized
/// implementation.
/// Note that `object_rel!` doesn't provide method
/// definitions unless explicitly specified by the user,
/// so this is effective the method called for all edges _unless_
/// overridden for a particular edge for a particular object
/// (see [`object::tpl`] as an example).
default fn pre_add_edge(
asg: &mut Asg,
_from_oi: ObjectIndex<O>,
_to_oi: ObjectIndex<OB>,
_ctx_span: Option<Span>,
commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError> {
Ok(commit(asg))
}

View File

@ -91,5 +91,3 @@ object_rel! {
// empty
}
}
impl<OB: ObjectRelatable> AsgRelMut<OB> for Doc {}

View File

@ -268,5 +268,3 @@ impl ObjectIndex<Expr> {
self.add_edge_from(asg, oi_container, None)
}
}
impl<OB: ObjectRelatable> AsgRelMut<OB> for Expr {}

View File

@ -1406,7 +1406,5 @@ impl ObjectIndex<Ident> {
}
}
impl<OB: ObjectRelatable> AsgRelMut<OB> for Ident {}
#[cfg(test)]
mod test;

View File

@ -306,5 +306,3 @@ impl ObjectIndex<Meta> {
self.add_edge_to(asg, oi_ref, Some(oi_ref.span()))
}
}
impl<OB: ObjectRelatable> AsgRelMut<OB> for Meta {}

View File

@ -147,5 +147,3 @@ impl ObjectIndex<Pkg> {
self.add_edge_to(asg, oi_doc, None)
}
}
impl<OB: ObjectRelatable> AsgRelMut<OB> for Pkg {}

View File

@ -58,7 +58,7 @@ macro_rules! object_rel {
(
$(#[$attr:meta])+
$from:ident -> {
$($ety:ident $kind:ident,)*
$($ety:ident $kind:ident $({$($impl:tt)*})?,)*
}
$(can_recurse($rec_obj:ident) if $rec_expr:expr)?
) => {paste::paste! {
@ -171,6 +171,17 @@ macro_rules! object_rel {
}
}
}
// This generates a specialized implementation _per target `$kind`_
// and allows for the caller to override methods on the trait.
// This takes advantage of trait specialization via
// `min_specialization`;
// see `AsgRelMut` for more information.
$(
impl AsgRelMut<$kind> for $from {
$( $($impl)* )?
}
)*
}};
// Static edge types.

View File

@ -70,5 +70,3 @@ impl ObjectIndex<Root> {
oi.add_edge_from(asg, *self, None)
}
}
impl<OB: ObjectRelatable> AsgRelMut<OB> for Root {}

View File

@ -167,7 +167,20 @@ object_rel! {
Tpl -> {
// Expressions must be able to be anonymous to allow templates in
// any `Expr` context.
tree Expr,
tree Expr {
fn pre_add_edge(
asg: &mut Asg,
from_oi: ObjectIndex<Self>,
to_oi: ObjectIndex<Expr>,
_ctx_span: Option<Span>,
commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError> {
let span = to_oi.resolve(asg).span();
from_oi.map_obj(asg, |tpl| tpl.overwrite(TplShape::Expr(span)));
Ok(commit(asg))
}
},
// Identifiers are used for both references and identifiers that
// will expand into an application site.
@ -238,40 +251,3 @@ impl ObjectIndex<Tpl> {
self.add_edge_to(asg, oi_doc, None)
}
}
impl AsgRelMut<Expr> for Tpl {
fn pre_add_edge(
asg: &mut Asg,
from_oi: ObjectIndex<Self>,
to_oi: ObjectIndex<Expr>,
_ctx_span: Option<Span>,
commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError> {
let span = to_oi.resolve(asg).span();
from_oi.map_obj(asg, |tpl| tpl.overwrite(TplShape::Expr(span)));
Ok(commit(asg))
}
}
// TODO: Merge this into the macro above
impl AsgRelMut<Ident> for Tpl {}
impl AsgRelMut<Tpl> for Tpl {}
impl AsgRelMut<Doc> for Tpl {}
// This uses `min_specialization` to satisfy trait bounds for
// `<ObjectIndexTo as ObjectIndexRelTo>::add_edge`.
// This will be better integrated in future commits.
// See message of the commit that introduced this comment for more
// information.
impl<OB: ObjectRelatable> AsgRelMut<OB> for Tpl {
default fn pre_add_edge(
asg: &mut Asg,
_from_oi: ObjectIndex<Self>,
_to_oi: ObjectIndex<OB>,
_ctx_span: Option<Span>,
commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError> {
Ok(commit(asg))
}
}