// 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(), } } } impl From> for ObjectIndexTo { fn from(value: ObjectIndexToTree) -> Self { match value { ObjectIndexToTree(oit) => oit, } } } /// 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(), } } } }