tamer: asg: Root package definition

This causes a package definition to be rooted (so that it can be easily
accessed for a graph walk).  This keeps consistent with the new
`ObjectIndex`-based API by introducing a unit `Root` `ObjectKind` and the
boilerplate that goes with it.

This boilerplate, now glaringly obvious, will be refactored at some point,
since its repetition is onerous and distracting.

DEV-13159
main
Mike Gerwitz 2023-01-31 22:00:51 -05:00
parent a7fee3071d
commit 2d3b27ac01
9 changed files with 189 additions and 26 deletions

View File

@ -488,7 +488,7 @@ impl ParseState for AirAggregate {
(st, Todo) => Transition(st).incomplete(),
(Empty(es), PkgOpen(span)) => {
let oi_pkg = asg.create(Pkg::new(span));
let oi_pkg = asg.create(Pkg::new(span)).root(asg);
Transition(PkgDfn(oi_pkg, es)).incomplete()
}

View File

@ -247,14 +247,22 @@ fn ident_root_existing() {
}
#[test]
fn empty_pkg() {
fn pkg_is_rooted() {
let toks = vec![Air::PkgOpen(S1), Air::PkgClose(S2)];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
// TODO: Packages aren't named via AIR at the time of writing,
// so we can't look anything up.
let asg = sut.finalize().unwrap().into_context();
let oi_root = asg.root(S3);
let pkg = oi_root
.edges_filtered::<Pkg>(&asg)
.next()
.expect("missing rooted package")
.resolve(&asg);
assert_eq!(pkg.span(), S1);
}
#[test]
@ -785,8 +793,7 @@ fn sibling_subexprs_have_ordered_edges_to_parent() {
let oi_root = asg.expect_ident_oi::<Expr>(id_root);
let siblings = oi_root
.edges(&asg)
.filter_map(ObjectRel::narrow::<Expr>)
.edges_filtered::<Expr>(&asg)
.map(ObjectIndex::cresolve(&asg))
.collect::<Vec<_>>();
@ -987,10 +994,7 @@ fn expr_ref_to_ident() {
let oi_foo = asg.expect_ident_oi::<Expr>(id_foo);
let foo_refs = oi_foo
.edges(&asg)
.filter_map(ObjectRel::narrow::<Ident>)
.collect::<Vec<_>>();
let foo_refs = oi_foo.edges_filtered::<Ident>(&asg).collect::<Vec<_>>();
// We should have only a single reference (to `id_bar`).
assert_eq!(foo_refs.len(), 1);

View File

@ -19,7 +19,7 @@
//! Abstract semantic graph.
use self::object::{ObjectRelFrom, ObjectRelTy, ObjectRelatable};
use self::object::{ObjectRelFrom, ObjectRelTy, ObjectRelatable, Root};
use super::{
AsgError, FragmentText, Ident, IdentKind, Object, ObjectIndex, ObjectKind,
@ -31,6 +31,7 @@ use crate::{
fmt::{DisplayWrapper, TtQuote},
global,
parse::{util::SPair, Token},
span::Span,
sym::SymbolId,
};
use petgraph::{
@ -155,13 +156,13 @@ impl Asg {
// Exhaust the first index to be used as a placeholder
// (its value does not matter).
let empty_node = graph.add_node(Object::Root.into());
let empty_node = graph.add_node(Object::Root(Root).into());
index.push(empty_node);
// Automatically add the root which will be used to determine what
// identifiers ought to be retained by the final program.
// This is not indexed and is not accessable by name.
let root_node = graph.add_node(Object::Root.into());
let root_node = graph.add_node(Object::Root(Root).into());
Self {
graph,
@ -276,10 +277,16 @@ impl Asg {
.map_err(Into::into)
}
// TODO: This is transitional;
// remove once [`crate::xmlo::asg_builder`] is removed.
pub fn root(&self) -> NodeIndex<Ix> {
self.root_node
/// Root object.
///
/// All [`Object`]s reachable from the root will be included in the
/// compilation unit or linked executable.
///
/// The `witness` is used in the returned [`ObjectIndex`] and is
/// intended for diagnostic purposes to highlight the source entity that
/// triggered the request of the root.
pub fn root(&self, witness: Span) -> ObjectIndex<Root> {
ObjectIndex::new(self.root_node, witness)
}
/// Add an object as a root.

View File

@ -120,10 +120,12 @@ use std::{convert::Infallible, fmt::Display, marker::PhantomData};
pub mod expr;
pub mod ident;
pub mod pkg;
pub mod root;
pub use expr::Expr;
pub use ident::Ident;
pub use pkg::Pkg;
pub use root::Root;
/// An object on the ASG.
///
@ -136,7 +138,7 @@ pub enum Object {
/// the final executable.
///
/// There should be only one object of this kind.
Root,
Root(Root),
/// A package of identifiers.
Pkg(Pkg),
@ -169,7 +171,7 @@ pub enum ObjectRelTy {
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::Root(_) => write!(f, "root ASG node"),
Self::Pkg(pkg) => Display::fmt(pkg, f),
Self::Ident(ident) => Display::fmt(ident, f),
Self::Expr(expr) => Display::fmt(expr, f),
@ -180,7 +182,7 @@ impl Display for Object {
impl Object {
pub fn span(&self) -> Span {
match self {
Self::Root => UNKNOWN_SPAN,
Self::Root(_) => UNKNOWN_SPAN,
Self::Pkg(pkg) => pkg.span(),
Self::Ident(ident) => ident.span(),
Self::Expr(expr) => expr.span(),
@ -245,6 +247,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)
@ -263,6 +271,17 @@ impl From<Expr> for Object {
}
}
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.
@ -302,6 +321,15 @@ impl AsRef<Object> for Object {
}
}
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 {
@ -449,6 +477,21 @@ impl<O: ObjectKind> ObjectIndex<O> {
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<Item = ObjectIndex<OB>> + 'a
where
O: ObjectRelTo<OB> + 'a,
{
self.edges(asg).filter_map(ObjectRel::narrow::<OB>)
}
/// Incoming edges to self filtered by [`ObjectKind`] `OI`.
///
/// For filtering rationale,
@ -542,6 +585,22 @@ impl<O: ObjectKind> ObjectIndex<O> {
Some(ObjectIndex::<OB>(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<O>,
{
asg.root(self.span()).add_edge_to(asg, self);
self
}
}
impl ObjectIndex<Object> {

View File

@ -219,9 +219,9 @@ impl ObjectRelatable for Expr {
) -> 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::Pkg => None,
}
}
}

View File

@ -1000,9 +1000,9 @@ impl ObjectRelatable for Ident {
) -> 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::Pkg => None,
}
}
}

View File

@ -78,9 +78,9 @@ impl ObjectRelatable for 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::Pkg => None,
}
}
}
@ -93,7 +93,7 @@ impl From<ObjectIndex<Ident>> for PkgRel {
impl ObjectIndex<Pkg> {
/// Indicate that the given identifier `oi` is defined in this package.
pub fn defines(&self, asg: &mut Asg, oi: ObjectIndex<Ident>) -> Self {
pub fn defines(self, asg: &mut Asg, oi: ObjectIndex<Ident>) -> Self {
self.add_edge_to(asg, oi)
}
}

View File

@ -0,0 +1,93 @@
// Root of the 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 <http://www.gnu.org/licenses/>.
//! Root node of the ASG.
use std::fmt::Display;
use super::{
Ident, Object, ObjectIndex, ObjectKind, ObjectRel, ObjectRelTy,
ObjectRelatable, Pkg,
};
/// A unit [`Object`] type representing the root node.
///
/// This exists for consistency with the rest of the object API,
/// and for use with [`ObjectIndex`].
#[derive(Debug, PartialEq)]
pub struct Root;
impl Display for Root {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ASG 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 for RootRel {
fn narrow<OB: ObjectKind + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
match self {
Self::Ident(oi) => oi.filter_rel(),
Self::Pkg(oi) => oi.filter_rel(),
}
}
}
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,
}
}
}
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)
}
}

View File

@ -55,7 +55,7 @@ where
// TODO: Encapsulate petgraph.
// This is technically a topological sort, but functions have cycles.
let mut dfs = DfsPostOrder::new(&asg.graph, asg.root());
let mut dfs = DfsPostOrder::new(&asg.graph, asg.root(UNKNOWN_SPAN).into());
// These are always generated by the map compiler,
// but do not have edges that would allow them to be properly ordered
@ -69,7 +69,7 @@ where
let oi = ObjectIndex::<Object>::new(index, UNKNOWN_SPAN);
match asg.get(oi).expect("missing object") {
Object::Root => (),
Object::Root(_) => (),
Object::Ident(ident) => dest.push(ident)?,
obj @ (Object::Pkg(_) | Object::Expr(_)) => {