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-13708
main
Mike Gerwitz 2023-02-03 15:53:50 -05:00
parent 52e5242af2
commit 89700aa949
6 changed files with 189 additions and 24 deletions

View File

@ -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();

View File

@ -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.

View File

@ -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 {

View File

@ -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>,

View File

@ -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 {

View File

@ -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 {