tamer: asg::graph::object::ObjectRel::is_cross_edge: New trait method
This introduces the concept of ontological cross edges. The term "cross edge" is most often seen in the context of graph traversals, e.g. the trees formed by a depth-first search. This, however, refers to the trees that are inherent in the ontology of the graph. For example, an `ExprRef` will produce a cross edge to the referenced `Ident`, that that is a different tree than the current expression. (Well, I suppose technically it _could_ be a back edge, but then that'd be a cycle which would fail the process once we get to preventing it. So let's ignore that for now.) DEV-13708main
parent
52e5242af2
commit
89700aa949
|
@ -22,6 +22,7 @@
|
|||
|
||||
use super::super::Ident;
|
||||
use super::*;
|
||||
use crate::asg::graph::object::expr::ExprRel;
|
||||
use crate::asg::graph::object::ObjectRel;
|
||||
use crate::parse::Parser;
|
||||
use crate::{
|
||||
|
@ -458,6 +459,12 @@ fn expr_non_empty_ident_root() {
|
|||
// Identifiers should reference the same expression.
|
||||
let expr_b = asg.expect_ident_obj::<Expr>(id_b);
|
||||
assert_eq!(expr_a, expr_b);
|
||||
|
||||
// Ontological sanity check:
|
||||
// Child expressions must not be considered cross edges since they are
|
||||
// part of the same tree.
|
||||
let oi_expr_a = asg.expect_ident_oi::<Expr>(id_a);
|
||||
assert!(!oi_expr_a.edges(&asg).any(|rel| rel.is_cross_edge()));
|
||||
}
|
||||
|
||||
// Binding an identifier after a child expression means that the parser is
|
||||
|
@ -994,12 +1001,22 @@ fn expr_ref_to_ident() {
|
|||
|
||||
let oi_foo = asg.expect_ident_oi::<Expr>(id_foo);
|
||||
|
||||
let foo_refs = oi_foo.edges_filtered::<Ident>(&asg).collect::<Vec<_>>();
|
||||
let mut foo_rels = oi_foo
|
||||
.edges(&asg)
|
||||
.filter_map(ExprRel::narrows_into::<Ident>)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// We should have only a single reference (to `id_bar`).
|
||||
assert_eq!(foo_refs.len(), 1);
|
||||
assert_eq!(foo_rels.len(), 1);
|
||||
|
||||
let oi_ident_bar = foo_refs[0];
|
||||
// Ontological sanity check:
|
||||
// references to identifiers should count as cross edges.
|
||||
// This is very important to ensure that certain graph traversals work
|
||||
// correctly between trees.
|
||||
assert!(foo_rels.iter().all(|rel| rel.is_cross_edge()));
|
||||
|
||||
let oi_ident_bar =
|
||||
foo_rels.pop().and_then(ExprRel::narrow::<Ident>).unwrap();
|
||||
let ident_bar = oi_ident_bar.resolve(&asg);
|
||||
assert_eq!(ident_bar.span(), id_bar.span());
|
||||
|
||||
|
@ -1074,6 +1091,12 @@ fn idents_share_defining_pkg() {
|
|||
|
||||
assert_eq!(oi_foo.src_pkg(&asg).unwrap(), oi_bar.src_pkg(&asg).unwrap());
|
||||
|
||||
// Ontological sanity check:
|
||||
// edges from the package to identifiers defined by it should not be
|
||||
// considered cross edges.
|
||||
let oi_pkg = oi_foo.src_pkg(&asg).unwrap();
|
||||
assert!(oi_pkg.edges(&asg).all(|rel| !rel.is_cross_edge()));
|
||||
|
||||
// Missing identifiers should not have a source package,
|
||||
// since we don't know what defined it yet.
|
||||
let oi_baz = asg.lookup(id_baz).unwrap();
|
||||
|
|
|
@ -672,7 +672,11 @@ impl<O: ObjectKind> From<ObjectIndex<O>> for Span {
|
|||
pub trait ObjectRelTo<OB: ObjectKind + ObjectRelatable> =
|
||||
ObjectRelatable where <Self as ObjectRelatable>::Rel: From<ObjectIndex<OB>>;
|
||||
|
||||
pub(super) trait ObjectRelFrom<OA: ObjectKind + ObjectRelatable> =
|
||||
/// Reverse of [`ObjectRelTo`].
|
||||
///
|
||||
/// This is primarily useful for avoiding `where` clauses,
|
||||
/// or for use in `impl Trait` specifications.
|
||||
pub trait ObjectRelFrom<OA: ObjectKind + ObjectRelatable> =
|
||||
ObjectRelatable where <OA as ObjectRelatable>::Rel: From<ObjectIndex<Self>>;
|
||||
|
||||
/// Identify [`Self::Rel`] as a sum type consisting of the subset of
|
||||
|
@ -687,7 +691,7 @@ pub trait ObjectRelatable: ObjectKind {
|
|||
/// targets for edges from [`Self`].
|
||||
///
|
||||
/// See [`ObjectRel`] for more information.
|
||||
type Rel: ObjectRel;
|
||||
type Rel: ObjectRel<Self>;
|
||||
|
||||
/// The [`ObjectRelTy`] tag used to identify this [`ObjectKind`] as a
|
||||
/// target of a relation.
|
||||
|
@ -746,7 +750,7 @@ pub trait ObjectRelatable: ObjectKind {
|
|||
/// adhere to the prescribed ontology,
|
||||
/// provided that invariants are properly upheld by the
|
||||
/// [`asg`](crate::asg) module.
|
||||
pub trait ObjectRel {
|
||||
pub trait ObjectRel<OA: ObjectKind>: Sized {
|
||||
/// Attempt to narrow into the [`ObjectKind`] `OB`.
|
||||
///
|
||||
/// Unlike [`Object`] nodes,
|
||||
|
@ -760,9 +764,93 @@ pub trait ObjectRel {
|
|||
///
|
||||
/// This return value is well-suited for [`Iterator::filter_map`] to
|
||||
/// query for edges of particular kinds.
|
||||
fn narrow<OB: ObjectKind + ObjectRelatable>(
|
||||
fn narrow<OB: ObjectRelFrom<OA> + ObjectRelatable>(
|
||||
self,
|
||||
) -> Option<ObjectIndex<OB>>;
|
||||
|
||||
/// Attempt to narrow into the [`ObjectKind`] `OB`,
|
||||
/// but rather than returning the narrowed type,
|
||||
/// return `Option<Self>`.
|
||||
///
|
||||
/// This can be used with [`Iterator::filter_map`].
|
||||
/// By not being a [`bool`] predicate,
|
||||
/// we're able to provide a default trait implementation based on
|
||||
/// [`Self::narrow`] without requiring that [`Self`] implement
|
||||
/// [`Copy`].
|
||||
fn narrows_into<OB: ObjectRelFrom<OA> + ObjectRelatable>(
|
||||
self,
|
||||
) -> Option<Self>
|
||||
where
|
||||
Self: From<ObjectIndex<OB>>,
|
||||
{
|
||||
self.narrow::<OB>().map(Into::into)
|
||||
}
|
||||
|
||||
/// Whether this relationship represents an ontological cross edge.
|
||||
///
|
||||
/// A _cross edge_ is an edge between two trees as described by the
|
||||
/// graph's ontology.
|
||||
/// Many objects on the graph represent trees,
|
||||
/// but contain references to other trees.
|
||||
/// Recognizing cross edges allows the system to understand when it is
|
||||
/// following an edge between two trees,
|
||||
/// which may require different behavior.
|
||||
///
|
||||
/// This contrasts to cross edges in the context of a graph traversal,
|
||||
/// where a tree is determined by a walk of the graph and may not take
|
||||
/// into consideration the meaning of edges.
|
||||
///
|
||||
/// _Because this is a property of the ontology rather than a structural
|
||||
/// interpretation of the graph,
|
||||
/// it must be manually verified by a human._
|
||||
/// An incorrect flagging of cross edges here will result in certain
|
||||
/// traversals being incorrect.
|
||||
///
|
||||
/// Implementation Context
|
||||
/// ======================
|
||||
/// It is important to understand why this method exists and how it may
|
||||
/// be used so that implementations of this trait do the right thing
|
||||
/// with regards to determining whether an edge ought to represent a
|
||||
/// cross edge.
|
||||
///
|
||||
/// For example,
|
||||
/// when generating a representation of an [`Expr`],
|
||||
/// a walk of the graph ought not consider an [`Ident`] reference to
|
||||
/// be part of the expression tree,
|
||||
/// otherwise the referenced expression would be inlined.
|
||||
/// Furthermore,
|
||||
/// visiting the referenced [`Ident`] ought not inhibit a later walk,
|
||||
/// since the walk must later traverse the [`Ident`] to reach the
|
||||
/// [`Object`] that it represents.
|
||||
/// Similarly,
|
||||
/// if the [`Ident`] has already been visited by a previous walk,
|
||||
/// we want to _re-visit_ it to output a reference as part of the
|
||||
/// referencing [`Expr`].
|
||||
///
|
||||
/// However,
|
||||
/// this behavior is not always desirable.
|
||||
/// In the case of a topological sort of the graph for linking,
|
||||
/// cross edges ought to count as visitations since that dependency
|
||||
/// must be calculated before the expression that needs it,
|
||||
/// and we don't want to re-calculate it again later on.
|
||||
///
|
||||
/// The cross-edge is therefore an ontological fact,
|
||||
/// but its _interpretation_ is context-dependent.
|
||||
///
|
||||
/// Note that the ontology is not intended to support back edges,
|
||||
/// since they produce cycles,
|
||||
/// except for exceptional situations
|
||||
/// (e.g. function recursion which will hopefully be removed from
|
||||
/// the language in the future).
|
||||
/// With that said,
|
||||
/// if an edge could conceivably be a back edge and not be rejected
|
||||
/// from circular dependency checks,
|
||||
/// then do _not_ assume that it is a cross edge without further
|
||||
/// analysis,
|
||||
/// which may require adding the [`Asg`] or other data
|
||||
/// (like a path)
|
||||
/// as another parameter to this function.
|
||||
fn is_cross_edge(&self) -> bool;
|
||||
}
|
||||
|
||||
/// A container for an [`Object`] allowing for owned borrowing of data.
|
||||
|
|
|
@ -22,11 +22,14 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use super::{
|
||||
Asg, Ident, Object, ObjectIndex, ObjectKind, ObjectRel, ObjectRelTy,
|
||||
Asg, Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy,
|
||||
ObjectRelatable,
|
||||
};
|
||||
use crate::{f::Functor, num::Dim, parse::util::SPair, span::Span};
|
||||
|
||||
#[cfg(doc)]
|
||||
use super::ObjectKind;
|
||||
|
||||
/// Expression.
|
||||
///
|
||||
/// The [`Span`] of an expression should be expanded to encompass not only
|
||||
|
@ -195,8 +198,8 @@ pub enum ExprRel {
|
|||
Expr(ObjectIndex<Expr>),
|
||||
}
|
||||
|
||||
impl ObjectRel for ExprRel {
|
||||
fn narrow<OB: ObjectKind + ObjectRelatable>(
|
||||
impl ObjectRel<Expr> for ExprRel {
|
||||
fn narrow<OB: ObjectRelFrom<Expr> + ObjectRelatable>(
|
||||
self,
|
||||
) -> Option<ObjectIndex<OB>> {
|
||||
match self {
|
||||
|
@ -204,6 +207,19 @@ impl ObjectRel for ExprRel {
|
|||
Self::Expr(oi) => oi.filter_rel(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this is a cross edge to another tree.
|
||||
///
|
||||
/// An expression is inherently a tree,
|
||||
/// however it may contain references to other identifiers which
|
||||
/// represent their own trees.
|
||||
/// Any [`Ident`] reference is a cross edge.
|
||||
fn is_cross_edge(&self) -> bool {
|
||||
match self {
|
||||
Self::Ident(..) => true,
|
||||
Self::Expr(..) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectRelatable for Expr {
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
|
||||
use super::{
|
||||
super::{Asg, AsgError, ObjectIndex, ObjectKind},
|
||||
Expr, Object, ObjectRel, ObjectRelTo, ObjectRelTy, ObjectRelatable, Pkg,
|
||||
Expr, Object, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy,
|
||||
ObjectRelatable, Pkg,
|
||||
};
|
||||
use crate::{
|
||||
diagnose::{Annotate, Diagnostic},
|
||||
|
@ -976,8 +977,8 @@ pub enum IdentRel {
|
|||
Expr(ObjectIndex<Expr>),
|
||||
}
|
||||
|
||||
impl ObjectRel for IdentRel {
|
||||
fn narrow<OB: ObjectKind + ObjectRelatable>(
|
||||
impl ObjectRel<Ident> for IdentRel {
|
||||
fn narrow<OB: ObjectRelFrom<Ident> + ObjectRelatable>(
|
||||
self,
|
||||
) -> Option<ObjectIndex<OB>> {
|
||||
match self {
|
||||
|
@ -985,6 +986,24 @@ impl ObjectRel for IdentRel {
|
|||
Self::Expr(oi) => oi.filter_rel(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this edge is a cross edge to another tree.
|
||||
///
|
||||
/// Identifiers are either transparent
|
||||
/// (bound to a definition)
|
||||
/// or opaque.
|
||||
/// If transparent,
|
||||
/// then the identifier represents a definition,
|
||||
/// and is therefore a root to that definition.
|
||||
///
|
||||
/// Opaque identifiers at the time of writing are used by the linker
|
||||
/// which does not reason about cross edges
|
||||
/// (again at the time of writing).
|
||||
/// Consequently,
|
||||
/// this will always return [`false`].
|
||||
fn is_cross_edge(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectRelatable for Ident {
|
||||
|
@ -1078,7 +1097,7 @@ impl ObjectIndex<Ident> {
|
|||
///
|
||||
/// To bind an identifier,
|
||||
/// see [`Self::bind_definition`].
|
||||
pub fn is_bound_to<O: ObjectKind + ObjectRelatable>(
|
||||
pub fn is_bound_to<O: ObjectRelFrom<Ident> + ObjectRelatable>(
|
||||
&self,
|
||||
asg: &Asg,
|
||||
oi: ObjectIndex<O>,
|
||||
|
|
|
@ -19,14 +19,15 @@
|
|||
|
||||
//! Package object on the ASG.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{asg::Asg, span::Span};
|
||||
|
||||
use super::{
|
||||
Ident, Object, ObjectIndex, ObjectKind, ObjectRel, ObjectRelTy,
|
||||
Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy,
|
||||
ObjectRelatable,
|
||||
};
|
||||
use crate::{asg::Asg, span::Span};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg(doc)]
|
||||
use super::ObjectKind;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Pkg(Span);
|
||||
|
@ -58,14 +59,24 @@ pub enum PkgRel {
|
|||
Ident(ObjectIndex<Ident>),
|
||||
}
|
||||
|
||||
impl ObjectRel for PkgRel {
|
||||
fn narrow<OB: ObjectKind + ObjectRelatable>(
|
||||
impl ObjectRel<Pkg> for PkgRel {
|
||||
fn narrow<OB: ObjectRelFrom<Pkg> + ObjectRelatable>(
|
||||
self,
|
||||
) -> Option<ObjectIndex<OB>> {
|
||||
match self {
|
||||
Self::Ident(oi) => oi.filter_rel(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this is a cross edge to another package tree.
|
||||
///
|
||||
/// Packages serve as a root for all identifiers defined therein,
|
||||
/// and so an edge to [`Ident`] will never be a cross edge.
|
||||
///
|
||||
/// Imported [`Ident`]s do not have edges from this package.
|
||||
fn is_cross_edge(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectRelatable for Pkg {
|
||||
|
|
|
@ -22,10 +22,13 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use super::{
|
||||
Ident, Object, ObjectIndex, ObjectKind, ObjectRel, ObjectRelTy,
|
||||
Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy,
|
||||
ObjectRelatable, Pkg,
|
||||
};
|
||||
|
||||
#[cfg(doc)]
|
||||
use super::ObjectKind;
|
||||
|
||||
/// A unit [`Object`] type representing the root node.
|
||||
///
|
||||
/// This exists for consistency with the rest of the object API,
|
||||
|
@ -49,8 +52,8 @@ pub enum RootRel {
|
|||
Ident(ObjectIndex<Ident>),
|
||||
}
|
||||
|
||||
impl ObjectRel for RootRel {
|
||||
fn narrow<OB: ObjectKind + ObjectRelatable>(
|
||||
impl ObjectRel<Root> for RootRel {
|
||||
fn narrow<OB: ObjectRelFrom<Root> + ObjectRelatable>(
|
||||
self,
|
||||
) -> Option<ObjectIndex<OB>> {
|
||||
match self {
|
||||
|
@ -58,6 +61,11 @@ impl ObjectRel for RootRel {
|
|||
Self::Pkg(oi) => oi.filter_rel(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The root of the graph by definition has no cross edges.
|
||||
fn is_cross_edge(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectRelatable for Root {
|
||||
|
|
Loading…
Reference in New Issue