// Relationship between objects represented on ASG
//
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//! Relationship betwen objects on the ASG.
//!
//! See (parent module)[super] for more information.
use super::{
Doc, Expr, Ident, Meta, Object, ObjectIndex, ObjectKind, OiPairObjectInner,
Pkg, Root,
};
use crate::{
asg::{graph::object::Tpl, Asg},
f::Functor,
parse::util::SPair,
span::Span,
};
use std::{fmt::Display, marker::PhantomData};
pub use super::ObjectTy as ObjectRelTy;
impl Display for ObjectRelTy {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// At the time of writing,
// this happens to be sufficient.
std::fmt::Debug::fmt(self, f)
}
}
/// Declare relations for an [`ObjectKind`].
///
/// This generates an [`ObjectRel`] type for the provided [`ObjectKind`] and
/// binds it to the kind using [`ObjectRelatable`].
///
/// Each relationship must be explicitly specified as either a `tree` or
/// `cross` edge.
/// For more information on cross edges,
/// see [`ObjectRel::is_cross_edge`].
macro_rules! object_rel {
(
$(#[$attr:meta])+
$from:ident -> {
$($ety:ident $kind:ident,)*
}
$(can_recurse($rec_obj:ident) if $rec_expr:expr)?
) => {paste::paste! {
/// Subset of [`ObjectKind`]s that are valid targets for edges from
#[doc=concat!("[`", stringify!($from), "`].")]
///
$(#[$attr])+
///
/// See [`ObjectRel`] for more information.
///
/// [`ObjectKind`]: crate::asg::ObjectKind
#[derive(Debug, PartialEq, Eq)]
pub enum [<$from Rel>] {
$($kind(ObjectIndex<$kind>),)*
}
impl ObjectRel<$from> for [<$from Rel>] {
fn narrow_ref + ObjectRelatable>(
&self,
) -> Option> {
match *self {
$(Self::$kind(oi) => oi.filter_rel(),)*
}
}
/// The root of the graph by definition has no cross edges.
fn is_cross_edge(
&self,
#[allow(unused_variables)] // used only for `dyn` edges
rel: &$crate::asg::graph::object::rel::DynObjectRel
) -> bool {
match self {
$(
Self::$kind(..) => object_rel!(@is_cross_edge $ety rel),
)*
#[allow(unreachable_patterns)] // for empty Rel types
_ => unreachable!(
concat!(stringify!($from), "Rel is empty")
),
}
}
$(
fn can_recurse(&self, asg: &Asg) -> bool {
self.narrow_ref::<$from>()
.map(|oi| oi.resolve(asg))
.map(|$rec_obj| $rec_expr)
.unwrap_or(false)
}
)?
}
impl ObjectRelatable for $from {
type Rel = [<$from Rel>];
fn rel_ty() -> ObjectRelTy {
ObjectRelTy::$from
}
fn new_rel_dyn(
ty: ObjectRelTy,
#[allow(unused_variables)] // for empty Rel
oi: ObjectIndex,
) -> Option<[<$from Rel>]> {
match ty {
$(
ObjectRelTy::$kind => {
Some(Self::Rel::$kind(oi.must_narrow_into()))
},
)*
_ => None,
}
}
fn oi_rel_to_dyn(
#[allow(unused_variables)] // for empty Rel
oi: ObjectIndex,
) -> Option<$crate::asg::graph::object::ObjectIndexTo> {
#[allow(unused_imports)]
use $crate::asg::graph::object::ObjectIndexTo;
match OB::rel_ty() {
$(
ObjectRelTy::$kind => {
ObjectIndexTo::<$kind>::from(oi).reflexivity()
},
)*
_ => None,
}
}
}
$(
impl From> for [<$from Rel>] {
fn from(value: ObjectIndex<$kind>) -> Self {
Self::$kind(value)
}
}
object_rel!{ @impl_rel_to $from $ety $kind }
)*
impl From<[<$from Rel>]> for ObjectIndex {
fn from(value: [<$from Rel>]) -> Self {
match value {
$(
[<$from Rel>]::$kind(oi) => oi.widen(),
)*
}
}
}
}};
// Static edge types.
(@is_cross_edge cross $_:ident) => { true };
(@is_cross_edge tree $_:ident) => { false };
// Dynamic edge type.
//
// We consider an edge to be a cross edge iff it contains a context
// span.
// If it were _not_ a cross edge,
// then the edge would represent ownership,
// and the span information on the target would be sufficient context
// on its own;
// an edge only needs supplemental span information if there is
// another context in which that object is referenced.
(@is_cross_edge dyn $rel:ident) => { $rel.ctx_span().is_some() };
// 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 tree $kind:ident) => {
impl ObjectTreeRelTo<$kind> for $from {}
};
(@impl_rel_to $from:ident dyn $kind:ident) => {
// It _could_ be a tree edge;
// we can't know statically.
impl ObjectTreeRelTo<$kind> for $from {}
};
}
/// A dynamic relationship (edge) from one object to another before it has
/// been narrowed.
///
/// The source and target of this edge are usually [`ObjectIndex`]es,
/// but it is made generic (`S, T`) to support mapping while retaining
/// useful metadata,
/// e.g. to resolve an object while retaining the edge information.
#[derive(Debug, PartialEq)]
pub struct DynObjectRel, T = ObjectIndex>(
(ObjectRelTy, ObjectRelTy),
(S, T),
Option,
);
impl DynObjectRel {
pub(in super::super) fn new(
from_ty: ObjectRelTy,
to_ty: ObjectRelTy,
src: S,
target: T,
ctx_span: Option,
) -> Self {
Self((from_ty, to_ty), (src, target), ctx_span)
}
/// The type of the source edge.
pub fn source_ty(&self) -> ObjectRelTy {
match self {
Self((ty, _), ..) => *ty,
}
}
/// The type of the target edge.
pub fn target_ty(&self) -> ObjectRelTy {
match self {
Self((_, ty), ..) => *ty,
}
}
/// The source of this relationship.
pub fn source(&self) -> &S {
match self {
Self(_, (oi, _), _) => oi,
}
}
/// The target of this relationship.
///
/// This type generally originates as [`ObjectIndex`] but can be mapped
/// over to retain the structured edge data.
pub fn target(&self) -> &T {
match self {
Self(_, (_, oi), _) => oi,
}
}
/// A [`Span`] associated with the _relationship_ between the source and
/// target objects,
/// if any.
pub fn ctx_span(&self) -> Option {
match self {
Self(_, _, ctx_span) => *ctx_span,
}
}
}
impl DynObjectRel> {
/// Attempt to narrow the target into the [`ObjectRel`] of `O`.
///
/// See [`ObjectRelatable::new_rel_dyn`] for more information.
pub fn narrow_target(
&self,
) -> Option {
O::new_rel_dyn(self.target_ty(), *self.target())
}
/// Attempt to convert [`Self`] into an [`ObjectIndex`] with an
/// [`ObjectKind`] of type `O`.
///
/// This method allows marrying a dynamically determined type with a
/// context requiring a static type.
/// If the type `O` does not match the type stored at runtime,
/// [`None`] is returned.
pub fn filter_into_target(
&self,
) -> Option> {
if self.target_ty() == O::rel_ty() {
Some(self.target().must_narrow_into())
} else {
None
}
}
/// Pair the target [`ObjectIndex`] with its resolved [`Object`].
///
/// This allows the [`ObjectIndex`] to be refined alongside the inner
/// [`ObjectKind`] so that callers can make use of the refined
/// [`ObjectIndex`] without having to explicitly narrow themselves.
/// While isn't any more or less safe than the manual alternative,
/// it _does_ defend against logic bugs.
pub fn resolve_target_oi_pair(
self,
asg: &Asg,
) -> DynObjectRel> {
self.map(|(soi, toi)| (soi, toi.resolve(asg).pair_oi(toi)))
}
/// Retrieve the target [`ObjectIndex`] as an [`ObjectIndexTo`](ObjectIndexTo),
/// if the object can be related to objects of type `OB`.
///
/// This method may be confusing in that it represents another
/// _possible_ relation on top of the relation represented by
/// [`Self`].
/// That is:
///
/// ```text
/// OA -> OB [ -> OC]
/// '______'
/// Self
/// ```
///
/// If this method returns [`Some`],
/// that means that the target of this relation `OB` is an object
/// _that is capable of being related to_ an object of type `OC`.
pub fn target_oi_rel_to_dyn(
&self,
) -> Option> {
// TODO: A newtype ought to couple these in a way that we don't have
// to trust this assumption!
// This requires assuming that the target is of the target type,
// which _should_ certainly be the case if originating from the graph,
// but if it's not,
// then later resolving the `ObjectIndex` with a mismatched type
// will result in a panic.
self.target_ty()
.assuming_oi_maybe_rel_to_dyn(*self.target())
}
/// Dynamically determine whether this edge represents a cross edge.
///
/// This function is intended for _dynamic_ edge types,
/// which cannot be determined statically;
/// it should be used only in situations where the potential edge types
/// are unbounded,
/// e.g. on an iterator yielding generalized [`ObjectIndex`]es during
/// a full graph traversal.
/// You should otherwise use [`ObjectRel::is_cross_edge`].
///
/// For more information on cross edges,
/// see [`ObjectRel::is_cross_edge`].
pub fn is_cross_edge(&self) -> bool {
/// Generate cross-edge mappings between ObjectRelTy and the associated
/// ObjectRel.
///
/// This is intended to both reduce boilerplate and to eliminate typos.
///
/// This mess will be optimized away,
/// but exists so that cross edge definitions can exist alongside
/// other relationship definitions for each individual object type,
/// rather than having to maintain them in aggregate here.
macro_rules! ty_cross_edge {
($($ty:ident),*) => {
match self.source_ty() {
$(
ObjectRelTy::$ty => {
self.narrow_target::<$ty>().is_some_and(
|rel| rel.is_cross_edge(self)
)
},
)*
}
}
}
ty_cross_edge!(Root, Pkg, Ident, Expr, Tpl, Meta, Doc)
}
/// Dynamically determine whether this edge represents a permitted
/// cycle.
///
/// A cycle is permitted in certain cases of recursion.
/// See [`ObjectRel::can_recurse`] for more information.
pub fn can_recurse(&self, asg: &Asg) -> bool {
macro_rules! ty_can_recurse {
($($ty:ident),*) => {
match self.source_ty() {
$(
ObjectRelTy::$ty => {
self.narrow_target::<$ty>().is_some_and(
|rel| rel.can_recurse(asg)
)
},
)*
}
}
}
ty_can_recurse!(Root, Pkg, Ident, Expr, Tpl, Meta, Doc)
}
}
impl DynObjectRel, T> {
/// Pair the source [`ObjectIndex`] with its resolved [`Object`].
///
/// This allows the [`ObjectIndex`] to be refined alongside the inner
/// [`ObjectKind`] so that callers can make use of the refined
/// [`ObjectIndex`] without having to explicitly narrow themselves.
/// While isn't any more or less safe than the manual alternative,
/// it _does_ defend against logic bugs.
pub fn resolve_source_oi_pair(
self,
asg: &Asg,
) -> DynObjectRel, T> {
self.map(|(soi, toi)| (soi.resolve(asg).pair_oi(soi), toi))
}
}
impl DynObjectRel, ObjectIndex> {
/// Pair the source and target [`ObjectIndex`]es with their respective
/// resolved [`Object`]s.
///
/// See [`Self::resolve_target_oi_pair`] and
/// [`Self::resolve_source_oi_pair`] for more information.
pub fn resolve_oi_pairs(
self,
asg: &Asg,
) -> DynObjectRel, Object>
{
self.resolve_source_oi_pair(asg).resolve_target_oi_pair(asg)
}
}
impl Functor<(S, T), (U, V)> for DynObjectRel {
type Target = DynObjectRel;
fn map(self, f: impl FnOnce((S, T)) -> (U, V)) -> Self::Target {
match self {
Self(tys, x, ctx_span) => DynObjectRel(tys, f(x), ctx_span),
}
}
}
impl Display for DynObjectRel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self((from_ty, to_ty), (s, t), _) = self;
write!(f, "dynamic edge {from_ty}->{to_ty} with {s}->{t}",)
}
}
/// Indicate that an [`ObjectKind`] `Self` can be related to
/// [`ObjectKind`] `OB` by creating an edge from `Self` to `OB`.
///
/// This trait defines a portion of the graph ontology,
/// allowing [`Self`] to be related to `OB` by creating a directed edge
/// from [`Self`] _to_ `OB`, as in:
///
/// ```text
/// (Self) -> (OB)
/// ```
///
/// While the data on the graph itself is dynamic and provided at runtime,
/// the systems that _construct_ the graph using the runtime data can be
/// statically analyzed by the type system to ensure that they only
/// construct graphs that adhere to this schema.
pub trait ObjectRelTo =
ObjectRelatable where ::Rel: From>;
/// Reverse of [`ObjectRelTo`].
///
/// This is primarily useful for avoiding `where` clauses,
/// or for use in `impl Trait` specifications.
pub trait ObjectRelFrom =
ObjectRelatable where ::Rel: From>;
/// Indicate that an [`ObjectKind`] `Self` _could possibly take ownership
/// over_ 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.
pub trait ObjectTreeRelTo:
ObjectRelTo
{
}
/// Identify [`Self::Rel`] as a sum type consisting of the subset of
/// [`Object`] variants representing the valid _target_ edges of
/// [`Self`].
///
/// This is used to derive [`ObjectRelTo``],
/// which can be used as a trait bound to assert a valid relationship
/// between two [`Object`]s.
pub trait ObjectRelatable: ObjectKind {
/// Sum type representing a subset of [`Object`] variants that are valid
/// targets for edges from [`Self`].
///
/// See [`ObjectRel`] for more information.
type Rel: ObjectRel;
/// The [`ObjectRelTy`] tag used to identify this [`ObjectKind`] as a
/// target of a relation.
fn rel_ty() -> ObjectRelTy;
/// Represent a relation to another [`ObjectKind`] that cannot be
/// statically known and must be handled at runtime.
///
/// A value of [`None`] means that the provided [`ObjectRelTy`] is not
/// valid for [`Self`].
/// If the caller is utilizing edge data that is already present on the graph,
/// then this means that the system is not properly upholding edge
/// invariants
/// (the graph's ontology)
/// and the system ought to panic;
/// this is a significant bug representing a problem with the
/// correctness of the system.
///
/// See [`ObjectRel`] for more information.
fn new_rel_dyn(
ty: ObjectRelTy,
oi: ObjectIndex,
) -> Option;
/// Cast the provided [`ObjectIndex`] into an [`ObjectIndexTo`] if it is
/// able to be related to the provided `OB`.
///
/// This is intended to be used in a dynamic context,
/// where the caller is not aware statically of the [`ObjectKind`]s
/// involved.
/// If it is _required_ that an object be relatable,
/// use [`ObjectRelTo`] to statically verify that assertion.
///
/// If the type `OB` is not a valid target of a relation from this type,
/// [`None`] will be returned.
fn oi_rel_to_dyn(
oi: ObjectIndex,
) -> Option>;
}
impl ObjectIndex {
pub fn rel_ty(&self) -> ObjectRelTy {
O::rel_ty()
}
}
/// A relationship to another [`ObjectKind`].
///
/// This trait is intended to be implemented by enums that represent the
/// subset of [`ObjectKind`]s that are able to serve as edge targets for
/// the [`ObjectRelatable`] that utilizes it as its
/// [`ObjectRelatable::Rel`].
///
/// As described in the [module-level documentation](super),
/// the concrete [`ObjectKind`] of an edge is generally not able to be
/// determined statically outside of code paths that created the
/// [`Object`] anew.
/// But we _can_ at least narrow the types of [`ObjectKind`]s to those
/// [`ObjectRelTo`]s that we know are valid,
/// since the system is restricted (statically) to those edges when
/// performing operations on the graph.
///
/// This [`ObjectRel`] represents that subset of [`ObjectKind`]s.
/// A caller may decide to dispatch based on the type of edge it receives,
/// or it may filter edges with [`Self::narrow`] in conjunction with
/// [`Iterator::filter_map`]
/// (for example).
/// Since the wrapped value is an [`ObjectIndex`],
/// the system will eventually panic if it attempts to reference a node
/// that is not of the type expected by the edge,
/// which can only happen if the edge has an incorrect [`ObjectRelTy`],
/// meaning the graph is somehow corrupt
/// (because system invariants were not upheld).
///
/// This affords us both runtime memory safety and static guarantees that
/// the system is not able to generate an invalid graph that does not
/// adhere to the prescribed ontology,
/// provided that invariants are properly upheld by the
/// [`asg`](crate::asg) module.
pub trait ObjectRel:
Sized + Into>
{
/// Attempt to narrow into the [`ObjectKind`] `OB`.
///
/// Unlike [`Object`] nodes,
/// _this operation does not panic_,
/// instead returning an [`Option`].
/// If the relationship is of type `OB`,
/// then [`Some`] will be returned with an inner
/// [`ObjectIndex`](ObjectIndex).
/// If the narrowing fails,
/// [`None`] will be returned instead.
///
/// This return value is well-suited for [`Iterator::filter_map`] to
/// query for edges of particular kinds.
fn narrow + ObjectRelatable>(
self,
) -> Option> {
self.narrow_ref()
}
/// Attempt to narrow into the [`ObjectKind`] `OB`.
///
/// This method is the same as [`Self::narrow`],
/// but taking a reference instead of ownership.
fn narrow_ref + ObjectRelatable>(
&self,
) -> Option>;
/// Attempt to narrow into the [`ObjectKind`] `OB`,
/// but rather than returning the narrowed type,
/// return `Option`.
///
/// 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 + ObjectRelatable>(
self,
) -> Option
where
Self: From>,
{
self.narrow::().map(Into::into)
}
/// Widen into an [`ObjectIndex`],
/// discarding static type information.
fn widen(self) -> ObjectIndex {
self.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 introducing more context to this method.
///
/// Dynamic Cross Edges
/// ==================
/// Sometimes a cross edge cannot be determined statically due to
/// ontological compromises.
/// For example,
/// a [`Tpl`]->[`Ident`] edge could be a reference to another tree,
/// as in the case of template application,
/// or the template could be serving as a container for that
/// definition.
/// The former case is a cross edge,
/// but the ladder is not.
///
/// We could introduce a layer of indirection on the graph to
/// disambiguate,
/// but that adds complexity to the graph that many different
/// subsystems need to concern themselves with.
/// But cross edge determination is fairly isolated,
/// needed only by traversals.
///
/// Therefore,
/// this method also receives a [`DynObjectRel`] reference,
/// which contains edge information.
/// The edge can be augmented with data that helps to determine,
/// at runtime,
/// whether that particular edge is a cross edge.
///
/// The use of [`DynObjectRel`] is heavy and effectively makes this
/// method useless for callers that do not deal directly with raw
/// [`Asg`] data;
/// it may be useful to refine this further in the future to correct
/// that,
/// once all use cases are clear.
fn is_cross_edge(&self, rel: &DynObjectRel) -> bool;
/// Whether the provided relationship represents a valid recursive
/// target.
///
/// It is expected that this method will be consulted only when the
/// provided [`ObjectIndex`] would produce a cycle when added to some
/// path.
/// This means that the source and target object will be identical.
///
/// It is expected that a cycle should be able to be "cut" at this point
/// while still producing a valid topological ordering of the graph.
/// For example,
/// consider two mutually recursive functions `A` and `B`,
/// as shown here:
///
/// ```text
/// A -> B
/// ^----'
/// ```
///
/// There are two cycles that might be encountered:
///
/// - `A -> B -> A`, which would cut to `A -> B`; and
/// - `B -> A -> B`, which would cut to `B -> A`.
///
/// In both cases,
/// since both `A` and `B` are functions,
/// a valid ordering is produced.
///
/// Failure to uphold this invariant when designing the graph's ontology
/// will result in an invalid ordering of the graph,
/// which will compile a program that does not behave according to
/// its specification.
/// That is:
/// proper ordering is a requirement to uphold soundness.
///
/// Recursion will continue to be limited as TAMER progresses,
/// migrating to a more APL-like alternative to solving
/// otherwise-recursive problems and restricting remaining recursion
/// to that which can provably terminate.
fn can_recurse(&self, _asg: &Asg) -> bool {
false
}
}
/// An [`ObjectIndex`]-like object that is able to relate to
/// [`ObjectKind`] `OB`.
///
/// This serves primarily as an opaque [`ObjectIndex`] that we know can be
/// related to some other type of [`ObjectKind`] `OB`.
/// This allows for generic graph operations that operate on relationships
/// without having to know the type of the source object (`Self`).
pub trait ObjectIndexRelTo: Sized + Clone + Copy {
/// The [`ObjectRelTy`] of the inner [`ObjectIndex`] before widening.
fn src_rel_ty(&self) -> ObjectRelTy;
/// Widen this type into a generic [`ObjectIndex`] with no
/// [`ObjectKind`] information.
///
/// See [`ObjectIndex::widen`] for more information.
fn widen(&self) -> ObjectIndex;
/// Add an 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.
/// To create edges to other types of objects,
/// and for more information about this operation
/// (including `ctx_span`),
/// see [`ObjectIndex::add_edge_to`].
fn add_edge_to(
self,
asg: &mut Asg,
to_oi: ObjectIndex,
ctx_span: Option,
) -> Self {
asg.add_edge(self, to_oi, ctx_span);
self
}
/// Check whether an edge exists from `self` to `to_oi`.
fn has_edge_to(&self, asg: &Asg, to_oi: ObjectIndex) -> bool {
asg.has_edge(*self, to_oi)
}
/// Indicate that the given identifier `oi` is defined by this object.
fn defines(self, asg: &mut Asg, oi: ObjectIndex) -> Self
where
Self: ObjectIndexRelTo,
{
self.add_edge_to(asg, oi, None)
}
/// Iterate over the [`ObjectIndex`]es of the outgoing edges of `self`
/// that match the [`ObjectKind`] `OB`.
///
/// Since this trait only guarantees edges to `OB`,
/// only edges targeting that type will be returned;
/// other types of edges may or may not exist.
/// See also [`ObjectIndex::edges_filtered`].
fn edges_rel_to<'a>(
&self,
asg: &'a Asg,
) -> impl Iterator- > + 'a {
asg.edges_dyn(self.widen())
.filter_map(|rel| rel.filter_into_target())
}
/// Attempt to look up a locally bound [`Ident`] via a linear search of
/// `self`'s edges.
///
/// Performance
/// ===========
/// _This is a linear (O(1)) search of the edges of the node
/// corresponding to `self`!_
/// At the time of writing,
/// edges are stored using index references in a manner similar to a
/// linked list (petgraph).
/// And for each such edge,
/// the target object must be resolved so that its
/// [`SymbolId`](crate::sym::SymbolId) may be retrieved and compared
/// against the provided `name`.
///
/// If the number of edges is small and the objects are fairly localized
/// in memory relative to `self`,
/// then this may not be a concern.
/// However,
/// if you've arrived at this method while investigating unfavorable
/// circumstances during profiling,
/// then you should consider caching like the global environment
/// (see [`Asg::lookup`]).
fn lookup_local_linear(
&self,
asg: &Asg,
name: SPair,
) -> Option
>
where
Self: ObjectIndexRelTo,
{
// Rust fails to infer OB with `self.edges_rel_to` as of 2023-03
ObjectIndexRelTo::::edges_rel_to(self, asg)
.find(|oi| oi.resolve(asg).name().symbol() == name.symbol())
}
/// Declare a local identifier.
///
/// A local identifier is lexically scoped to `self`.
/// This operation is valid only for [`ObjectKind`]s that can contain
/// edges to [`Ident`]s.
///
/// TODO: This allows for duplicate local identifiers!
fn declare_local(&self, asg: &mut Asg, name: SPair) -> ObjectIndex
where
Self: ObjectIndexRelTo,
{
asg.create(Ident::declare(name))
.add_edge_from(asg, *self, None)
}
}
impl ObjectIndexRelTo
for ObjectIndex
where
O: ObjectRelTo,
{
fn src_rel_ty(&self) -> ObjectRelTy {
O::rel_ty()
}
fn widen(&self) -> ObjectIndex {
ObjectIndex::::widen(*self)
}
}
impl ObjectIndexRelTo for ObjectIndexTo {
fn src_rel_ty(&self) -> ObjectRelTy {
match self {
Self((_, ty), _) => *ty,
}
}
fn widen(&self) -> ObjectIndex {
*self.as_ref()
}
}
impl ObjectIndexRelTo for ObjectIndexToTree {
fn src_rel_ty(&self) -> ObjectRelTy {
match self {
Self(oito) => oito.src_rel_ty(),
}
}
fn widen(&self) -> ObjectIndex {
match self {
Self(oito) => oito.widen(),
}
}
}
impl From> for ObjectIndex {
fn from(oi_rel: ObjectIndexTo) -> Self {
oi_rel.widen()
}
}
impl AsRef> for ObjectIndexTo {
fn as_ref(&self) -> &ObjectIndex {
match self {
Self((oi, _), _) => oi,
}
}
}
/// An [`ObjectIndex`]-like object that is able to create a _tree_ 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 ObjectIndexTreeRelTo:
ObjectIndexRelTo + Into>
{
}
impl ObjectIndexTreeRelTo for ObjectIndexToTree {}
impl ObjectIndexTreeRelTo
for ObjectIndex
where
O: ObjectTreeRelTo,
{
}
pub use private::{ObjectIndexTo, ObjectIndexToTree};
/// Private inner module to ensure that nothing is able to bypass invariants
/// by constructing [`ObjectIndexTo`] manually.
mod private {
use std::hash::{Hash, Hasher};
use super::*;
/// Some [`ObjectIndex`] that is able to [`ObjectRelTo`] `OB`.
///
/// This type upholds the invariant that any [`ObjectIndex`] contained
/// within is [`ObjectRelTo`] `OB`.
/// This allows this object to serve in place of a concrete
/// [`ObjectIndex`] for graph operations that need only know whether
/// an object is relatable to another,
/// such as when adding edges.
///
/// This object is only needed when relations need to be manipulated on
/// a known target [`ObjectKind`] `OB`,
/// but the source [`ObjectKind`] is dynamic.
/// This is necessary because a generic [`Object`] must first be
/// narrowed before being able to be used in any graph operation so
/// that the ontology can be statically enforced.
///
/// See [`ObjectIndexRelTo`] for more information.
///
/// Constructing [`ObjectIndexTo`]
/// ==============================
/// This object is intended to be constructed using [`From`].
/// _Never construct this object in any other way;_
/// manually creating the struct will not uphold its invariants,
/// which can lead to an invalid graph construction,
/// which will in turn lead to internal system failures when trying
/// to operate on the graph data down the line.
/// There are no memory safety concerns.
#[derive(Debug)]
pub struct ObjectIndexTo(
(ObjectIndex, ObjectRelTy),
PhantomData,
);
impl From>
for ObjectIndexTo
where
O: ObjectRelTo,
{
fn from(oi: ObjectIndex) -> Self {
Self(oi.widen_dyn_ty(), PhantomData::default())
}
}
impl ObjectIndexTo {
pub fn span(&self) -> Span {
(*self).into()
}
/// Assert a reflexive relationship between `OB` and `OC`.
///
/// The types `OB` and `OC` are equivalent (and therefore reflexive)
/// iff they have matching `ObjectRelTy`s.
///
/// The sole purpose of this method is to satisfy Rust's type system
/// in dynamic situations where the type system is not able to
/// understand what we're doing,
/// where the type `OC` is more general than the type `OB`.
/// This method is always safe;
/// it will return [`None`] if the two types differ in runtime
/// value.
///
/// While the term "reflexive" is a binary relation in mathematics,
/// the term "reflexivity" originates from the Coq tactic.
///
/// For an example of where this is needed,
/// see [`ObjectRelatable::oi_rel_to_dyn`].
pub fn reflexivity(
self,
) -> Option> {
let Self(parts, _) = self;
(OB::rel_ty() == OC::rel_ty())
.then_some(ObjectIndexTo(parts, PhantomData::default()))
}
}
// Ignore metadata that should always be consistent with the underlying
// `ObjectIndex`.
impl PartialEq for ObjectIndexTo {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self((oi, _), _), Self((oi_other, _), _)) => oi == oi_other,
}
}
}
impl Eq for ObjectIndexTo {}
// Ignore metadata that should always be consistent with the underlying
// `ObjectIndex`.
impl Hash for ObjectIndexTo {
fn hash(&self, state: &mut H) {
match self {
Self((oi, _), _) => oi.hash(state),
}
}
}
// Deriving `Clone`/`Copy` as of 2023-03 was introducing a
// `Clone`/`Copy` bound on `OB`.
impl Clone for ObjectIndexTo {
fn clone(&self) -> Self {
Self(self.0, self.1)
}
}
impl Copy for ObjectIndexTo {}
impl From> for Span {
fn from(oi: ObjectIndexTo) -> Self {
match oi {
ObjectIndexTo((oi, _), _) => oi.span(),
}
}
}
/// Some [`ObjectIndex`] that can create a _tree_ edge to `OB`.
///
/// This is a specialization of
/// (and contains)
/// [`ObjectIndexTo`];
/// see that for more information.
///
/// See also [`ObjectIndexTreeRelTo`].
#[derive(Debug)]
pub struct ObjectIndexToTree(ObjectIndexTo);
impl From>
for ObjectIndexToTree
where
O: ObjectTreeRelTo,
{
fn from(oi: ObjectIndex) -> Self {
Self(oi.into())
}
}
// Deriving any of the below were introducing trait bounds on `OB`.
impl PartialEq for ObjectIndexToTree {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self(oi), Self(oi_other)) => oi == oi_other,
}
}
}
impl Eq for ObjectIndexToTree {}
impl Hash for ObjectIndexToTree {
fn hash(&self, state: &mut H) {
match self {
Self(oi) => oi.hash(state),
}
}
}
impl Clone for ObjectIndexToTree {
fn clone(&self) -> Self {
Self(self.0)
}
}
impl Copy for ObjectIndexToTree {}
impl From> for Span {
fn from(oi: ObjectIndexToTree) -> Self {
match oi {
ObjectIndexToTree(inner) => inner.span(),
}
}
}
}