tamer: asg::graph: object_gen and object_rel macros

The previous commit demonstrated the amount of boilerplate necessary for
introducing new `ObjectKind`s; this abstracts away a lot of that
boilerplate, and allows for declarative relationship definition for the
ASG's ontology.

DEV-13708
main
Mike Gerwitz 2023-02-25 23:56:05 -05:00
parent 454b91dfce
commit dd2232b58b
7 changed files with 263 additions and 523 deletions

View File

@ -117,10 +117,12 @@ use crate::{
use petgraph::graph::NodeIndex;
use std::{convert::Infallible, fmt::Display, marker::PhantomData};
#[macro_use]
mod rel;
pub mod expr;
pub mod ident;
pub mod pkg;
mod rel;
pub mod root;
pub mod tpl;
@ -134,131 +136,174 @@ pub use rel::{
pub use root::Root;
pub use tpl::Tpl;
/// An object on the ASG.
/// Given a list of [`ObjectKind`]s,
/// generate [`Object`],
/// associated types,
/// and various [`From`]/[`AsRef`] implementations for [`ObjectKind`]
/// widening and narrowing.
///
/// 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<T: ObjectInner = OnlyObjectInner> {
/// 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<T: ObjectInner = OnlyObjectInner> {
$(
$(#[$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
$(<Self as ObjectInner>::$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<T: DisplayableObjectInner> Display for Object<T> {
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<Object> 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(T::Root),
Root,
/// A package of identifiers.
Pkg(T::Pkg),
Pkg,
/// Identifier (a named object).
Ident(T::Ident),
Ident,
/// Expression.
///
/// An expression may optionally be named by one or more [`Ident`]s.
Expr(T::Expr),
Expr,
/// A template definition.
Tpl(T::Tpl),
}
/// The collection of potential objects of [`Object`].
pub trait ObjectInner {
type Root;
type Pkg;
type Ident;
type Expr;
type Tpl;
}
/// An [`ObjectInner`] where each constituent type implements [`Display`].
trait DisplayableObjectInner = ObjectInner
where
<Self as ObjectInner>::Root: Display,
<Self as ObjectInner>::Pkg: Display,
<Self as ObjectInner>::Ident: Display,
<Self as ObjectInner>::Expr: Display,
<Self as ObjectInner>::Tpl: Display;
/// Concrete [`ObjectKind`]s and nothing more.
#[derive(Debug, PartialEq)]
pub struct OnlyObjectInner;
impl ObjectInner for OnlyObjectInner {
type Root = Root;
type Pkg = Pkg;
type Ident = Ident;
type Expr = Expr;
type Tpl = Tpl;
}
/// 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 Root = &'a Root;
type Pkg = &'a Pkg;
type Ident = &'a Ident;
type Expr = &'a Expr;
type Tpl = &'a Tpl;
}
/// A [`RefObjectInner`] paired with an [`ObjectIndex`] representing 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 Root = (&'a Root, ObjectIndex<Root>);
type Pkg = (&'a Pkg, ObjectIndex<Pkg>);
type Ident = (&'a Ident, ObjectIndex<Ident>);
type Expr = (&'a Expr, ObjectIndex<Expr>);
type Tpl = (&'a Tpl, ObjectIndex<Tpl>);
}
impl<T: DisplayableObjectInner> Display for Object<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Root(root) => Display::fmt(root, f),
Self::Pkg(pkg) => Display::fmt(pkg, f),
Self::Ident(ident) => Display::fmt(ident, f),
Self::Expr(expr) => Display::fmt(expr, f),
Self::Tpl(tpl) => Display::fmt(tpl, f),
}
}
}
/// 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::Root($inner) => Object::Root($map),
Object::Pkg($inner) => Object::Pkg($map),
Object::Ident($inner) => Object::Ident($map),
Object::Expr($inner) => Object::Expr($map),
Object::Tpl($inner) => Object::Tpl($map),
}
};
Tpl,
}
impl Object<OnlyObjectInner> {
@ -364,142 +409,12 @@ impl From<&Object> for Span {
}
}
impl From<Root> for Object {
fn from(root: Root) -> Self {
Self::Root(root)
}
}
impl From<Pkg> for Object {
fn from(pkg: Pkg) -> Self {
Self::Pkg(pkg)
}
}
impl From<Ident> for Object {
fn from(ident: Ident) -> Self {
Self::Ident(ident)
}
}
impl From<Expr> for Object {
fn from(expr: Expr) -> Self {
Self::Expr(expr)
}
}
impl From<Tpl> for Object {
fn from(tpl: Tpl) -> Self {
Self::Tpl(tpl)
}
}
impl From<Object> for Root {
/// Narrow an object into an [`Root`],
/// panicing if the object is not of that type.
fn from(val: Object) -> Self {
match val {
Object::Root(root) => root,
_ => val.narrowing_panic("the root"),
}
}
}
impl From<Object> for Pkg {
/// Narrow an object into an [`Ident`],
/// panicing if the object is not of that type.
fn from(val: Object) -> Self {
match val {
Object::Pkg(pkg) => pkg,
_ => val.narrowing_panic("a package"),
}
}
}
impl From<Object> for Ident {
/// Narrow an object into an [`Ident`],
/// panicing if the object is not of that type.
fn from(val: Object) -> Self {
match val {
Object::Ident(ident) => ident,
_ => val.narrowing_panic("an identifier"),
}
}
}
impl From<Object> for Expr {
/// Narrow an object into an [`Expr`],
/// panicing if the object is not of that type.
fn from(val: Object) -> Self {
match val {
Object::Expr(expr) => expr,
_ => val.narrowing_panic("an expression"),
}
}
}
impl From<Object> for Tpl {
/// Narrow an object into an [`Expr`],
/// panicing if the object is not of that type.
fn from(val: Object) -> Self {
match val {
Object::Tpl(tpl) => tpl,
_ => val.narrowing_panic("a template"),
}
}
}
impl AsRef<Object> for Object {
fn as_ref(&self) -> &Object {
self
}
}
impl AsRef<Root> for Object {
fn as_ref(&self) -> &Root {
match self {
Object::Root(ref root) => root,
_ => self.narrowing_panic("the root"),
}
}
}
impl AsRef<Pkg> for Object {
fn as_ref(&self) -> &Pkg {
match self {
Object::Pkg(ref pkg) => pkg,
_ => self.narrowing_panic("a package"),
}
}
}
impl AsRef<Ident> for Object {
fn as_ref(&self) -> &Ident {
match self {
Object::Ident(ref ident) => ident,
_ => self.narrowing_panic("an identifier"),
}
}
}
impl AsRef<Expr> for Object {
fn as_ref(&self) -> &Expr {
match self {
Object::Expr(ref expr) => expr,
_ => self.narrowing_panic("an expression"),
}
}
}
impl AsRef<Tpl> for Object {
fn as_ref(&self) -> &Tpl {
match self {
Object::Tpl(ref tpl) => tpl,
_ => self.narrowing_panic("a template"),
}
}
}
/// An [`Object`]-compatbile entity.
///
/// See [`ObjectIndex`] for more information.

View File

@ -207,70 +207,14 @@ impl Display for DimState {
}
}
/// Subset of [`ObjectKind`]s that are valid targets for edges from
/// [`Expr`].
///
/// See [`ObjectRel`] for more information.
#[derive(Debug, PartialEq, Eq)]
pub enum ExprRel {
Ident(ObjectIndex<Ident>),
Expr(ObjectIndex<Expr>),
}
impl ObjectRel<Expr> for ExprRel {
fn narrow<OB: ObjectRelFrom<Expr> + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
match self {
Self::Ident(oi) => oi.filter_rel(),
Self::Expr(oi) => oi.filter_rel(),
}
}
/// Whether this is a cross edge to another tree.
///
object_rel! {
/// An expression is inherently a tree,
/// however it may contain references to other identifiers which
/// represent their own trees.
/// Any [`Ident`] reference is a cross edge.
fn is_cross_edge(&self) -> bool {
match self {
Self::Ident(..) => true,
Self::Expr(..) => false,
}
}
}
impl ObjectRelatable for Expr {
type Rel = ExprRel;
fn rel_ty() -> ObjectRelTy {
ObjectRelTy::Expr
}
fn new_rel_dyn(
ty: ObjectRelTy,
oi: ObjectIndex<Object>,
) -> Option<ExprRel> {
match ty {
ObjectRelTy::Root => None,
ObjectRelTy::Pkg => None,
ObjectRelTy::Ident => Some(ExprRel::Ident(oi.must_narrow_into())),
ObjectRelTy::Expr => Some(ExprRel::Expr(oi.must_narrow_into())),
ObjectRelTy::Tpl => None,
}
}
}
impl From<ObjectIndex<Ident>> for ExprRel {
fn from(value: ObjectIndex<Ident>) -> Self {
Self::Ident(value)
}
}
impl From<ObjectIndex<Expr>> for ExprRel {
fn from(value: ObjectIndex<Expr>) -> Self {
Self::Expr(value)
Expr -> {
cross Ident,
tree Expr,
}
}

View File

@ -967,28 +967,7 @@ pub struct Source {
pub override_: bool,
}
/// Subset of [`ObjectKind`]s that are valid targets for edges from
/// [`Ident`].
///
/// See [`ObjectRel`] for more information.
#[derive(Debug, PartialEq, Eq)]
pub enum IdentRel {
Ident(ObjectIndex<Ident>),
Expr(ObjectIndex<Expr>),
}
impl ObjectRel<Ident> for IdentRel {
fn narrow<OB: ObjectRelFrom<Ident> + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
match self {
Self::Ident(oi) => oi.filter_rel(),
Self::Expr(oi) => oi.filter_rel(),
}
}
/// Whether this edge is a cross edge to another tree.
///
object_rel! {
/// Identifiers are either transparent
/// (bound to a definition)
/// or opaque.
@ -1001,41 +980,9 @@ impl ObjectRel<Ident> for IdentRel {
/// (again at the time of writing).
/// Consequently,
/// this will always return [`false`].
fn is_cross_edge(&self) -> bool {
false
}
}
impl ObjectRelatable for Ident {
type Rel = IdentRel;
fn rel_ty() -> ObjectRelTy {
ObjectRelTy::Ident
}
fn new_rel_dyn(
ty: ObjectRelTy,
oi: ObjectIndex<Object>,
) -> Option<IdentRel> {
match ty {
ObjectRelTy::Root => None,
ObjectRelTy::Pkg => None,
ObjectRelTy::Ident => Some(IdentRel::Ident(oi.must_narrow_into())),
ObjectRelTy::Expr => Some(IdentRel::Expr(oi.must_narrow_into())),
ObjectRelTy::Tpl => None,
}
}
}
impl From<ObjectIndex<Ident>> for IdentRel {
fn from(value: ObjectIndex<Ident>) -> Self {
Self::Ident(value)
}
}
impl From<ObjectIndex<Expr>> for IdentRel {
fn from(value: ObjectIndex<Expr>) -> Self {
Self::Expr(value)
Ident -> {
tree Ident,
tree Expr,
}
}

View File

@ -58,56 +58,13 @@ impl Functor<Span> for Pkg {
}
}
/// Subset of [`ObjectKind`]s that are valid targets for edges from
/// [`Ident`].
///
/// See [`ObjectRel`] for more information.
#[derive(Debug, PartialEq, Eq)]
pub enum PkgRel {
Ident(ObjectIndex<Ident>),
}
impl ObjectRel<Pkg> for PkgRel {
fn narrow<OB: ObjectRelFrom<Pkg> + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
match self {
Self::Ident(oi) => oi.filter_rel(),
}
}
/// Whether this is a cross edge to another package tree.
///
object_rel! {
/// Packages serve as a root for all identifiers defined therein,
/// and so an edge to [`Ident`] will never be a cross edge.
///
/// Imported [`Ident`]s do not have edges from this package.
fn is_cross_edge(&self) -> bool {
false
}
}
impl ObjectRelatable for Pkg {
type Rel = PkgRel;
fn rel_ty() -> ObjectRelTy {
ObjectRelTy::Pkg
}
fn new_rel_dyn(ty: ObjectRelTy, oi: ObjectIndex<Object>) -> Option<PkgRel> {
match ty {
ObjectRelTy::Root => None,
ObjectRelTy::Pkg => None,
ObjectRelTy::Ident => Some(PkgRel::Ident(oi.must_narrow_into())),
ObjectRelTy::Expr => None,
ObjectRelTy::Tpl => None,
}
}
}
impl From<ObjectIndex<Ident>> for PkgRel {
fn from(value: ObjectIndex<Ident>) -> Self {
Self::Ident(value)
Pkg -> {
tree Ident,
}
}

View File

@ -30,23 +30,7 @@ use crate::{
};
use std::fmt::Display;
/// 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,
Tpl,
}
pub use super::ObjectTy as ObjectRelTy;
impl Display for ObjectRelTy {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
@ -56,6 +40,95 @@ impl Display for ObjectRelTy {
}
}
/// Declare relations for an [`ObjectKind`].
///
/// This generates an [`ObjectRel`] type for the provided [`ObjectKind`] and
/// binds it to the kind using [`ObjectRelatable`].
///
/// Each relationship must be explicitly specified as either a `tree` or
/// `cross` edge.
/// For more information on cross edges,
/// see [`ObjectRel::is_cross_edge`].
macro_rules! object_rel {
(
$(#[$attr:meta])+
$from:ident -> {
$($ety:ident $kind:ident,)*
}
) => {paste::paste! {
/// Subset of [`ObjectKind`]s that are valid targets for edges from
#[doc=concat!("[`", stringify!($from), "`].")]
///
$(#[$attr])+
///
/// See [`ObjectRel`] for more information.
///
/// [`ObjectKind`]: crate::asg::ObjectKind
#[derive(Debug, PartialEq, Eq)]
pub enum [<$from Rel>] {
$($kind(ObjectIndex<$kind>),)*
}
impl ObjectRel<$from> for [<$from Rel>] {
fn narrow<OB: ObjectRelFrom<$from> + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
match self {
$(Self::$kind(oi) => oi.filter_rel(),)*
}
}
/// The root of the graph by definition has no cross edges.
fn is_cross_edge(&self) -> bool {
match self {
$(
Self::$kind(..) => object_rel!(@is_cross_edge $ety),
)*
#[allow(unreachable_patterns)] // for empty Rel types
_ => unreachable!(
concat!(stringify!($from), "Rel is empty")
),
}
}
}
impl ObjectRelatable for $from {
type Rel = [<$from Rel>];
fn rel_ty() -> ObjectRelTy {
ObjectRelTy::$from
}
fn new_rel_dyn(
ty: ObjectRelTy,
#[allow(unused_variables)] // for empty Rel
oi: ObjectIndex<Object>,
) -> Option<[<$from Rel>]> {
match ty {
$(
ObjectRelTy::$kind => {
Some(Self::Rel::$kind(oi.must_narrow_into()))
},
)*
_ => None,
}
}
}
$(
impl From<ObjectIndex<$kind>> for [<$from Rel>] {
fn from(value: ObjectIndex<$kind>) -> Self {
Self::$kind(value)
}
}
)*
}};
(@is_cross_edge cross) => { true };
(@is_cross_edge tree) => { false };
}
/// A dynamic relationship (edge) from one object to another before it has
/// been narrowed.
///

View File

@ -42,61 +42,10 @@ impl Display for Root {
}
}
/// Subset of [`ObjectKind`]s that are valid targets for edges from
/// [`Ident`].
///
/// See [`ObjectRel`] for more information.
#[derive(Debug, PartialEq, Eq)]
pub enum RootRel {
Pkg(ObjectIndex<Pkg>),
Ident(ObjectIndex<Ident>),
}
impl ObjectRel<Root> for RootRel {
fn narrow<OB: ObjectRelFrom<Root> + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
match self {
Self::Ident(oi) => oi.filter_rel(),
Self::Pkg(oi) => oi.filter_rel(),
}
}
object_rel! {
/// The root of the graph by definition has no cross edges.
fn is_cross_edge(&self) -> bool {
false
}
}
impl ObjectRelatable for Root {
type Rel = RootRel;
fn rel_ty() -> ObjectRelTy {
ObjectRelTy::Root
}
fn new_rel_dyn(
ty: ObjectRelTy,
oi: ObjectIndex<Object>,
) -> Option<RootRel> {
match ty {
ObjectRelTy::Root => None,
ObjectRelTy::Pkg => Some(RootRel::Pkg(oi.must_narrow_into())),
ObjectRelTy::Ident => Some(RootRel::Ident(oi.must_narrow_into())),
ObjectRelTy::Expr => None,
ObjectRelTy::Tpl => None,
}
}
}
impl From<ObjectIndex<Pkg>> for RootRel {
fn from(value: ObjectIndex<Pkg>) -> Self {
Self::Pkg(value)
}
}
impl From<ObjectIndex<Ident>> for RootRel {
fn from(value: ObjectIndex<Ident>) -> Self {
Self::Ident(value)
Root -> {
tree Pkg,
tree Ident,
}
}

View File

@ -42,54 +42,9 @@ impl Display for Tpl {
}
}
/// Subset of [`ObjectKind`]s that are valid targets for edges from
/// [`Tpl`].
///
/// See [`ObjectRel`] for more information.
#[derive(Debug, PartialEq, Eq)]
pub enum TplRel {
// TODO
}
impl ObjectRel<Tpl> for TplRel {
fn narrow<OB: ObjectRelFrom<Tpl> + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
None
}
/// Whether this is a cross edge to another tree.
///
/// An expression is inherently a tree,
/// however it may contain references to other identifiers which
/// represent their own trees.
/// Any [`Ident`] reference is a cross edge.
fn is_cross_edge(&self) -> bool {
false
object_rel! {
/// TODO
Tpl -> {
// ...
}
}
impl ObjectRelatable for Tpl {
type Rel = TplRel;
fn rel_ty() -> ObjectRelTy {
ObjectRelTy::Tpl
}
fn new_rel_dyn(
ty: ObjectRelTy,
_oi: ObjectIndex<Object>,
) -> Option<TplRel> {
match ty {
ObjectRelTy::Root => None,
ObjectRelTy::Pkg => None,
ObjectRelTy::Ident => None,
ObjectRelTy::Expr => None,
ObjectRelTy::Tpl => None,
}
}
}
impl ObjectIndex<Tpl> {
// TODO
}