// 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 static type-level guarantees that
//! the rest of the system does at compile-time.
//!
//! However,
//! we _are_ able to utilize the type system to statically ensure that
//! there exists no code path that is able to generate an invalid graph
//! (a graph that does not adhere to its ontology as described below).
//!
//! Every 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 an [`ObjectIndex`].
//! Consequently,
//! we cannot be _absolutely certain_ that the [`Object`] referred to by
//! an [`ObjectIndex`] is in fact 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,
//! it is conceivable that the system may contain,
//! now or in the future,
//! bugs that cause it to fail to uphold that invariant.
//!
//! 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 of [`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,
parse::util::SPair,
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 meta;
pub mod pkg;
pub mod root;
pub mod tpl;
pub use expr::Expr;
pub use ident::Ident;
pub use meta::Meta;
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