// 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::{Expr, Ident, Object, ObjectIndex, ObjectKind, Pkg, Root}; /// Object types corresponding to variants in [`Object`]. /// /// These are used as small tags for [`ObjectRelatable`]. /// Rust unfortunately makes working with its internal tags difficult, /// despite their efforts with [`std::mem::Discriminant`], /// which requires a _value_ to produce. /// /// TODO: Encapsulate within `crate::asg` when the graph can be better /// encapsulated. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ObjectRelTy { Root, Pkg, Ident, Expr, } /// Determine whether an edge from `from_ty` to `to_ty` is 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`]. /// /// The [`ObjectIndex`] `oi_to` represents the target object. /// It is not utilized at the time of writing, /// but is needed for internal data structures. /// /// For more information on cross edges, /// see [`ObjectRel::is_cross_edge`]. pub fn is_dyn_cross_edge( from_ty: ObjectRelTy, to_ty: ObjectRelTy, oi_to: ObjectIndex, ) -> 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 from_ty { $( ObjectRelTy::$ty => { $ty::new_rel_dyn(to_ty, oi_to).is_some_and( |rel| rel.is_cross_edge() ) }, )* } } } ty_cross_edge!(Root, Pkg, Ident, Expr) } /// 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>; /// 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; } 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 { /// 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>; /// 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) } /// 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; }