// 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.
//!
//! 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.
//!
//! Any node on the graph can represent any type of [`Object`].
//! An [`ObjectIndex`] contains an index into the graph,
//! _not_ a reference;
//! consequently,
//! it is 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.
//!
//! 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.
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::Display, marker::PhantomData};
pub mod expr;
pub mod ident;
use expr::Expr;
use ident::Ident;
/// An object on the ASG.
///
/// See the [module-level documentation](super) for more information.
#[derive(Debug, PartialEq)]
pub enum Object {
/// 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,
/// Identifier (a named object).
Ident(Ident),
/// Expression.
///
/// An expression may optionally be named by one or more [`Ident`]s.
Expr(Expr),
}
impl Display for Object {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Root => write!(f, "root ASG node"),
Self::Ident(ident) => Display::fmt(ident, f),
Self::Expr(expr) => Display::fmt(expr, f),
}
}
}
impl Object {
pub fn span(&self) -> Span {
match self {
Self::Root => UNKNOWN_SPAN,
Self::Ident(ident) => ident.span(),
Self::Expr(expr) => expr.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}",
)
}
}
impl From for Object {
fn from(ident: Ident) -> Self {
Self::Ident(ident)
}
}
impl From for Object {
fn from(expr: Expr) -> Self {
Self::Expr(expr)
}
}
impl From