// 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 . //! Objects represented by the ASG. //! //! ![Visualization of ASG ontology](../../ontviz.svg) //! //! Dynamic Object Types and Narrowing //! ================================== //! Unlike the functional lowering pipeline that precedes it, //! the ASG is a mutable, ever-evolving graph of dynamic data. //! The ASG does not benefit from the same type-level guarantees that the //! rest of the system does at compile-time. //! //! However, //! we _are_ able to utilize the type system to ensure statically that //! there exists no code path that is able to generated an invalid graph //! (a graph that does not adhere to its ontology as described below). //! //! Any node on the graph can represent any type of [`Object`]. //! An [`ObjectIndex`] contains an index into the graph, //! _not_ a reference; //! it is therefore possible (though avoidable) for objects to be //! modified out from underneath references. //! Consequently, //! we cannot trust that an [`ObjectIndex`] is what we expect it to be when //! performing an operation on the graph using that index, //! though the system is designed to uphold an invariant that the _type_ //! of [`Object`] cannot be changed. //! //! To perform an operation on a particular type of object, //! we must first _narrow_ it. //! Narrowing converts from the [`Object`] sum type into a more specific //! inner type, //! such as [`Ident`] or [`Expr`]. //! This operation _should_, //! if the compiler is operating correctly, //! always succeed, //! because the type of object should always match our expectations; //! the explicit narrowing is to ensure memory safety in case that //! assumption does not hold. //! To facilitate this in a convenient way, //! operations returning an [`ObjectIndex`] will be associated with an //! [`ObjectKind`] that will be used to automatically perform narrowing on //! subsequent operations using that [`ObjectIndex`]. //! //! Since a type mismatch represents a bug in the compiler, //! the API favors [`Result`]-free narrowing rather than burdening every //! caller with additional complexity---we //! will attempt to narrow and panic in the event of a failure, //! including a diagnostic message that helps to track down the issue //! using whatever [`Span`]s we have available. //! [`ObjectIndex`] is associated with a span derived from the point of its //! creation to handle this diagnostic situation automatically. //! //! Edge Types and Narrowing //! ------------------------ //! Unlike nodes, //! edges may reference [`Object`]s of many different types, //! as defined by the graph's ontology. //! //! The set [`ObjectKind`] types that may be related _to_ //! (via edges) //! from other objects are the variants of [`ObjectRelTy`]. //! Each such [`ObjectKind`] must implement [`ObjectRelatable`], //! where [`ObjectRelatable::Rel`] is an enum whose variants represent a //! _subset_ of [`Object`]'s variants that are valid targets for edges //! from that object type. //! If some [`ObjectKind`] `OA` is able to be related to another //! [`ObjectKind`] `OB`, //! then [`ObjectRelTo::`](ObjectRelTo) is implemented for `OA`. //! //! When querying the graph for edges using [`ObjectIndex::edges`], //! the corresponding [`ObjectRelatable::Rel`] type is provided, //! which may then be acted upon or filtered by the caller. //! Unlike nodes, //! it is difficult to statically expect exact edge types in most code //! paths //! (beyond the `Rel` object itself), //! and so [`ObjectRel::narrow`] produces an [`Option`] of the inner //! [`ObjectIndex`], //! rather than panicing. //! This `Option` is convenient to use with `Iterator::filter_map` to query //! for specific edge types. //! //! Using [`ObjectRelTo`], //! we are able to ensure statically that all code paths only add edges to //! the [`Asg`] that adhere to the ontology described above; //! it should therefore not be possible for an edge to exist on the //! graph that is not represented by [`ObjectRelatable::Rel`], //! provided that it is properly defined. //! Since [`ObjectRel`] narrows into an [`ObjectIndex`], //! the system will produce runtime panics if there is ever any attempt to //! follow an edge to an unexpected [`ObjectKind`]. use super::Asg; use crate::{ diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan}, diagnostic_panic, f::Functor, span::{Span, UNKNOWN_SPAN}, }; use petgraph::graph::NodeIndex; use std::{ convert::Infallible, fmt::{Debug, Display}, marker::PhantomData, }; #[macro_use] mod rel; pub mod expr; pub mod ident; pub mod pkg; pub mod root; pub mod tpl; pub use expr::Expr; pub use ident::Ident; pub use pkg::Pkg; pub use rel::{ DynObjectRel, ObjectRel, ObjectRelFrom, ObjectRelTo, ObjectRelTy, ObjectRelatable, }; pub use root::Root; pub use tpl::Tpl; /// Given a list of [`ObjectKind`]s, /// generate [`Object`], /// associated types, /// and various [`From`]/[`AsRef`] implementations for [`ObjectKind`] /// widening and narrowing. /// /// This macro must be applied only once. macro_rules! object_gen { ( $( $(#[$attr:meta])* $kind:ident, )+ ) => { /// An object on the ASG. /// /// This is generic over its inner values to support using /// [`Object`] as a sum type in a variety of different contexts /// where [`ObjectKind`] may be used. /// The concrete [`ObjectInner`] that is stored on the ASG itself is /// [`OnlyObjectInner`]. /// /// See the [module-level documentation](super) for more /// information. #[derive(Debug, PartialEq)] pub enum Object { $( $(#[$attr])* $kind(T::$kind), )+ } /// 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 ObjectTy { $($kind,)+ } /// The collection of potential objects of [`Object`]. pub trait ObjectInner { $(type $kind;)+ } /// An [`ObjectInner`] where each constituent type implements /// [`Display`]. trait DisplayableObjectInner = ObjectInner where $(::$kind: Display,)+; /// Concrete [`ObjectKind`]s and nothing more. #[derive(Debug, PartialEq)] pub struct OnlyObjectInner; impl ObjectInner for OnlyObjectInner { $(type $kind = $kind;)+ } /// References to [`OnlyObjectInner`]. /// /// This allows for `Object<&T>`. /// See [`Object::inner_as_ref`] for more information. #[derive(Debug, PartialEq)] pub struct RefObjectInner<'a>(PhantomData<&'a ()>); impl<'a> ObjectInner for RefObjectInner<'a> { $(type $kind = &'a $kind;)+ } /// A [`RefObjectInner`] paired with an [`ObjectIndex`] that /// represents it. /// /// This is desirable when an [`ObjectIndex`] is resolved but is /// still needed for graph operations. /// The pair ensures that, /// when the inner [`ObjectKind`] is narrowed, /// the paired [`ObjectIndex`] will too be narrowed to the same /// kind, /// limiting logic bugs that may result from otherwise having /// to manually narrow types. #[derive(Debug, PartialEq)] pub struct OiPairObjectInner<'a>(PhantomData<&'a ()>); impl<'a> ObjectInner for OiPairObjectInner<'a> { $(type $kind = (&'a $kind, ObjectIndex<$kind>);)+ } impl Display for Object { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { $(Self::$kind(x) => Display::fmt(x, f),)+ } } } $( impl From<$kind> for Object { fn from(x: $kind) -> Self { Self::$kind(x) } } impl From for $kind { /// Narrow an object into an [`Root`], /// panicing if the object is not of that type. fn from(obj: Object) -> Self { match obj { Object::$kind(x) => x, _ => obj.narrowing_panic(stringify!($kind)), } } } impl AsRef<$kind> for Object { fn as_ref(&self) -> &$kind { match self { Object::$kind(ref x) => x, _ => self.narrowing_panic(stringify!($kind)), } } } )+ /// Generate boilerplate `match`es for individual [`Object`] /// variants. /// /// Rust will infer the [`Object`]'s [`ObjectInner`] from the /// surrounding context. macro_rules! map_object { ($obj:expr, $inner:ident => $map:expr) => { match $obj { $(Object::$kind($inner) => Object::$kind($map),)+ } }; } } } object_gen! { /// Represents the root of all reachable identifiers. /// /// Any identifier not reachable from the root will not be linked into /// the final executable. /// /// There should be only one object of this kind. Root, /// A package of identifiers. Pkg, /// Identifier (a named object). Ident, /// Expression. /// /// An expression may optionally be named by one or more [`Ident`]s. Expr, /// A template definition. Tpl, } impl Object { pub fn span(&self) -> Span { match self { Self::Root(_) => UNKNOWN_SPAN, Self::Pkg(pkg) => pkg.span(), Self::Ident(ident) => ident.span(), Self::Expr(expr) => expr.span(), Self::Tpl(tpl) => tpl.span(), } } /// Retrieve an [`Ident`] reference, /// or [`None`] if the object is not an identifier. pub fn as_ident_ref(&self) -> Option<&Ident> { match self { Self::Ident(ident) => Some(ident), _ => None, } } /// Unwraps an object as an [`Ident`], /// panicing if the object is of a different type. /// /// This should be used only when a panic would represent an internal /// error resulting from state inconsistency on the graph. /// Ideally, /// the graph would be typed in such a way to prevent this type of /// thing from occurring in the future. pub fn unwrap_ident(self) -> Ident { self.into() } /// Unwraps an object as an [`&Ident`](Ident), /// panicing if the object is of a different type. /// /// This should be used only when a panic would represent an internal /// error resulting from state inconsistency on the graph. /// Ideally, /// the graph would be typed in such a way to prevent this type of /// thing from occurring in the future. pub fn unwrap_ident_ref(&self) -> &Ident { match self { Self::Ident(ident) => ident, x => panic!("internal error: expected Ident, found {x:?}"), } } /// Diagnostic panic after failing to narrow an object. /// /// This is an internal method. /// `expected` should contain "a"/"an". fn narrowing_panic(&self, expected: &str) -> ! { diagnostic_panic!( self.span() .internal_error(format!( "expected this object to be {expected}" )) .into(), "expected {expected}, found {self}", ) } /// Convert an `&Object` into an `Object<&Inner>`. /// /// Since [`Object`] is the type stored directly on the [`Asg`], /// this is necessary in order to use [`Object`] as a wrapper over /// narrowed [`ObjectKind`]s. fn inner_as_ref(&self) -> Object { map_object!(self, x => x) } /// Pair self with an [`ObjectIndex`] that represents it. /// /// This replaces the [`Span`] of the [`ObjectIndex`] with our span, /// such that any (unlikely) lookup errors will highlight this 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. /// /// Note that this makes no guarantees that the provided /// [`ObjectIndex`] `oi` is _actually_ an index that represents this /// object; /// that onus is on the caller. /// Getting this wrong won't lead to memory safety issues, /// but it will lead to bugs and confusion at best, /// and panics at worst if `oi` references a different [`ObjectKind`]. fn pair_oi(&self, oi: ObjectIndex) -> Object { map_object!( self.inner_as_ref(), x => (x, oi.must_narrow_into().overwrite(self.span())) ) } } impl From<&Object> for Span { fn from(val: &Object) -> Self { val.span() } } impl AsRef for Object { fn as_ref(&self) -> &Object { self } } /// An [`Object`]-compatbile entity. /// /// See [`ObjectIndex`] for more information. /// This type simply must be convertable both to and from [`Object`] so that /// operations on the graph that retrieve its value can narrow into it, /// and operations writing it back can expand it back into [`Object`]. /// /// Note that [`Object`] is also an [`ObjectKind`], /// if you do not desire narrowing. pub trait ObjectKind = Into where Self: Debug + PartialEq, Object: Into + AsRef; /// Index representing an [`Object`] stored on the [`Asg`](super::Asg). /// /// Object references are integer offsets, /// not pointers. /// See the [module-level documentation][self] for more information. /// /// The associated [`ObjectKind`] states an _expectation_ that, /// when this [`ObjectIndex`] is used to perform an operation on the ASG, /// that it will operate on an object of type `O`. /// This type will be verified at runtime during any graph operation, /// resulting in a panic if the expectation is not met; /// see the [module-level documentation][self] for more information. /// /// This object is associated with a [`Span`] that identifies the source /// location from which this object was derived; /// this is intended to be used to provide diagnostic information in the /// event that the object somehow becomes unavailable for later /// operations. /// /// _The span is not accounted for in [`PartialEq`]_, /// since it represents the context in which the [`ObjectIndex`] was /// retrieved, /// and the span associated with the underlying [`Object`] may evolve /// over time. #[derive(Debug)] pub struct ObjectIndex(NodeIndex, Span, PhantomData); impl Display for ObjectIndex { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let Self(ni, _, _) = self; write!(f, "ObjectIndex<{}>({ni:?})", O::rel_ty()) } } impl Display for ObjectIndex { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let Self(ni, _, _) = self; write!(f, "ObjectIndex({ni:?})") } } // Deriving this trait seems to silently fail at the time of writing // (2022-12-22, Rust 1.68.0-nightly). impl Clone for ObjectIndex { fn clone(&self) -> Self { Self(self.0, self.1, self.2) } } impl Copy for ObjectIndex {} impl ObjectIndex { pub fn new>(index: NodeIndex, span: S) -> Self { Self(index, span.into(), PhantomData::default()) } /// The source location from which the request for the associated object /// was derived. /// /// The span _does not necessarily represent_ the span of the target /// [`Object`]. /// If the object is being created, /// then it may, /// but it otherwise represents the location of whatever is /// _requesting_ the object. pub fn span(&self) -> Span { match self { Self(_, span, _) => *span, } } /// Add an edge from `self` to `to_oi` on the provided [`Asg`]. /// /// An edge can only be added if ontologically valid; /// see [`ObjectRelTo`] for more information. /// /// Edges may contain _contextual [`Span`]s_ if there is an important /// distinction to be made between a the span of a _reference_ to the /// target and the span of the target itself. /// This is of particular benefit to cross edges /// (see [`ObjectRel::is_cross_edge`]), /// which reference nodes from other trees in different contexts. /// /// See also [`Self::add_edge_to`]. pub fn add_edge_to( self, asg: &mut Asg, to_oi: ObjectIndex, ctx_span: Option, ) -> Self where O: ObjectRelTo, { asg.add_edge(self, to_oi, ctx_span); self } /// Add an edge from `from_oi` to `self` on the provided [`Asg`]. /// /// An edge can only be added if ontologically valid; /// see [`ObjectRelTo`] for more information. /// /// See also [`Self::add_edge_to`]. pub fn add_edge_from( self, asg: &mut Asg, from_oi: ObjectIndex, ctx_span: Option, ) -> Self where OB: ObjectRelTo, { from_oi.add_edge_to(asg, self, ctx_span); self } /// Create an iterator over the [`ObjectIndex`]es of the outgoing edges /// of `self`. /// /// Note that the [`ObjectKind`] `OB` indicates what type of /// [`ObjectIndex`]es will be yielded by the returned iterator; /// this method does nothing to filter non-matches. pub fn edges<'a>( self, asg: &'a Asg, ) -> impl Iterator::Rel> + 'a where O: ObjectRelatable + 'a, { asg.edges(self) } /// Iterate over the [`ObjectIndex`]es of the outgoing edges of `self` /// that match the [`ObjectKind`] `OB`. /// /// This is simply a shorthand for applying [`ObjectRel::narrow`] via /// [`Iterator::filter_map`]. pub fn edges_filtered<'a, OB: ObjectKind + ObjectRelatable + 'a>( self, asg: &'a Asg, ) -> impl Iterator> + 'a where O: ObjectRelTo + 'a, { self.edges(asg).filter_map(ObjectRel::narrow::) } /// Incoming edges to self filtered by [`ObjectKind`] `OI`. /// /// For filtering rationale, /// see [`Asg::incoming_edges_filtered`]. fn incoming_edges_filtered<'a, OI: ObjectKind + ObjectRelatable + 'a>( self, asg: &'a Asg, ) -> impl Iterator> + 'a where O: ObjectRelFrom + 'a, { asg.incoming_edges_filtered(self) } /// Resolve `self` to the object that it references. /// /// Panics /// ====== /// If our [`ObjectKind`] `O` does not match the actual type of the /// object on the graph, /// the system will panic. pub fn resolve(self, asg: &Asg) -> &O { asg.expect_obj(self) } /// Curried [`Self::resolve`]. pub fn cresolve<'a>(asg: &'a Asg) -> impl FnMut(Self) -> &'a O { move |oi| oi.resolve(asg) } /// Resolve the identifier and map over the resulting [`Object`] /// narrowed to [`ObjectKind`] `O`, /// replacing the object on the given [`Asg`]. /// /// While the provided map may be pure, /// this does mutate the provided [`Asg`]. /// /// If the operation fails, /// `f` is expected to provide an object /// (such as the original) /// to return to the graph. /// /// If this operation is [`Infallible`], /// see [`Self::map_obj`]. pub fn try_map_obj( self, asg: &mut Asg, f: impl FnOnce(O) -> Result, ) -> Result { asg.try_map_obj(self, f) } /// Resolve the identifier and infallibly map over the resulting /// [`Object`] narrowed to [`ObjectKind`] `O`, /// replacing the object on the given [`Asg`]. /// /// If this operation is _not_ [`Infallible`], /// see [`Self::try_map_obj`]. pub fn map_obj(self, asg: &mut Asg, f: impl FnOnce(O) -> O) -> Self { // This verbose notation (in place of e.g. `unwrap`) is intentional // to emphasize why it's unreachable and to verify our assumptions // at every point. match self.try_map_obj::(asg, |o| Ok(f(o))) { Ok(oi) => oi, Err::<_, Infallible>(_) => unreachable!(), } } /// Lift [`Self`] into [`Option`] and [`filter`](Option::filter) based /// on whether the [`ObjectRelatable::rel_ty`] of [`Self`]'s `O` /// matches that of `OB`. /// /// More intuitively: /// if `OB` is the same [`ObjectKind`] associated with [`Self`], /// return [`Some(Self)`](Some). /// Otherwise, /// return [`None`]. fn filter_rel( self, ) -> Option> where O: ObjectRelatable, { let Self(index, span, _pd) = self; // Rust doesn't know that `OB` and `O` will be the same, // but this will be the case. // If it weren't, // then [`ObjectIndex`] protects us at runtime, // so there are no safety issues here. Some(ObjectIndex::(index, span, PhantomData::default())) .filter(|_| O::rel_ty() == OB::rel_ty()) } /// Root this object in the ASG. /// /// A rooted object is forced to be reachable. /// This should only be utilized when necessary for toplevel objects; /// other objects should be reachable via their relations to other /// objects. /// Forcing objects to be reachable can prevent them from being /// optimized away if they are not used. pub fn root(self, asg: &mut Asg) -> Self where Root: ObjectRelTo, { asg.root(self.span()).add_edge_to(asg, self, None); self } /// Widen an [`ObjectKind`] `O` into [`Object`], /// generalizing the index type. /// /// This generalization is useful in dynamic contexts, /// but it discards type information that must be later re-checked and /// verified. pub fn widen(self) -> ObjectIndex { match self { Self(index, span, _pd) => ObjectIndex::new(index, span), } } } impl ObjectIndex { /// Indicate that the [`Object`] referenced by this index must be /// narrowed into [`ObjectKind`] `O` when resolved. /// /// This simply narrows the expected [`ObjectKind`]. pub fn must_narrow_into(self) -> ObjectIndex { match self { Self(index, span, _) => ObjectIndex::new(index, span), } } } impl Functor for ObjectIndex { fn map(self, f: impl FnOnce(Span) -> Span) -> Self { match self { Self(index, span, ph) => Self(index, f(span), ph), } } } impl PartialEq for ObjectIndex { /// Compare two [`ObjectIndex`]s' indices, /// without concern for its associated [`Span`]. /// /// See [`ObjectIndex`] for more information on why the span is not /// accounted for in this comparison. fn eq(&self, other: &Self) -> bool { match (self, other) { (Self(index_a, _, _), Self(index_b, _, _)) => index_a == index_b, } } } impl Eq for ObjectIndex {} impl From> for NodeIndex { fn from(objref: ObjectIndex) -> Self { match objref { ObjectIndex(index, _, _) => index, } } } impl From> for Span { fn from(value: ObjectIndex) -> Self { match value { ObjectIndex(_, span, _) => span, } } } /// A container for an [`Object`] allowing for owned borrowing of data. /// /// The purpose of allowing this owned borrowing is to permit a functional /// style of object manipulation, /// like the rest of the TAMER system, /// despite the mutable underpinnings of the ASG. /// This is accomplished by wrapping each object in an [`Option`] so that we /// can [`Option::take`] its inner value temporarily. /// /// This container has a critical invariant: /// the inner [`Option`] must _never_ be [`None`] after a method exits, /// no matter what branches are taken. /// Methods operating on owned data enforce this invariant by mapping over /// data and immediately placing the new value to the container before the /// method completes. /// This container will panic if this variant is not upheld. /// /// TODO: Make this `pub(super)` when [`Asg`]'s public API is cleaned up. #[derive(Debug, PartialEq)] pub struct ObjectContainer(Option); impl ObjectContainer { /// Retrieve an immutable reference to the inner [`Object`], /// narrowed to expected type `O`. /// /// Panics /// ====== /// This will panic if the object on the graph is not the expected /// [`ObjectKind`] `O`. pub fn get(&self) -> &O { let Self(container) = self; container .as_ref() .diagnostic_unwrap(container_oops) .as_ref() } /// Attempt to modify the inner [`Object`], /// narrowed to expected type `O`, /// returning any error. /// /// See also [`Self::replace_with`] if the operation is [`Infallible`]. /// /// Panics /// ====== /// This will panic if the object on the graph is not the expected /// [`ObjectKind`] `O`. pub fn try_replace_with( &mut self, f: impl FnOnce(O) -> Result, ) -> Result<(), E> { let ObjectContainer(container) = self; let obj = container.take().diagnostic_unwrap(container_oops).into(); // NB: We must return the object to the container in all code paths! let result = f(obj) .map(|obj| { container.replace(obj.into()); }) .map_err(|(orig, err)| { container.replace(orig.into()); err }); debug_assert!(container.is_some()); result } /// Modify the inner [`Object`], /// narrowed to expected type `O`. /// /// See also [`Self::try_replace_with`] if the operation can fail. /// /// Panics /// ====== /// This will panic if the object on the graph is not the expected /// [`ObjectKind`] `O`. pub fn replace_with(&mut self, f: impl FnOnce(O) -> O) { let _ = self.try_replace_with::(|obj| Ok(f(obj))); } } impl> From for ObjectContainer { fn from(obj: I) -> Self { Self(Some(obj.into())) } } fn container_oops() -> Vec> { // This used to be a real span, // but since this invariant is easily verified and should absolutely // never occur, // there's no point in complicating the API. let span = UNKNOWN_SPAN; vec![ span.help("this means that some operation used take() on the object"), span.help(" container but never replaced it with an updated object"), span.help( " after the operation completed, which should not \ be possible.", ), ] }