From 2b2776f4e19248fbdeae55c427f90be4d4e07d68 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 8 Feb 2023 15:15:30 -0500 Subject: [PATCH] tamer: asg::graph::object::rel: Extract object relationships DEV-13708 --- tamer/src/asg/graph/object.rs | 273 +--------------------------- tamer/src/asg/graph/object/rel.rs | 292 ++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 268 deletions(-) create mode 100644 tamer/src/asg/graph/object/rel.rs diff --git a/tamer/src/asg/graph/object.rs b/tamer/src/asg/graph/object.rs index b5024144..fbe067b9 100644 --- a/tamer/src/asg/graph/object.rs +++ b/tamer/src/asg/graph/object.rs @@ -120,11 +120,16 @@ use std::{convert::Infallible, fmt::Display, marker::PhantomData}; pub mod expr; pub mod ident; pub mod pkg; +mod rel; pub mod root; pub use expr::Expr; pub use ident::Ident; pub use pkg::Pkg; +pub use rel::{ + is_dyn_cross_edge, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy, + ObjectRelatable, +}; pub use root::Root; /// An object on the ASG. @@ -152,69 +157,6 @@ pub enum Object { Expr(Expr), } -/// 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: `pub(super)` 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(super) 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) -} - impl Display for Object { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { @@ -713,211 +655,6 @@ impl From> for Span { } } -/// 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; -} - /// A container for an [`Object`] allowing for owned borrowing of data. /// /// The purpose of allowing this owned borrowing is to permit a functional diff --git a/tamer/src/asg/graph/object/rel.rs b/tamer/src/asg/graph/object/rel.rs new file mode 100644 index 00000000..d576ecdf --- /dev/null +++ b/tamer/src/asg/graph/object/rel.rs @@ -0,0 +1,292 @@ +// 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; +}