tamer: asg: Introduce edge from Package to Ident

Included in this diff are the corresponding changes to the graph to support
the change.  Adding the edge was easy, but we also need a way to get the
package for an identifier.  The easiest way to do that is to modify the edge
weight to include not just the target node type, but also the source.

DEV-13159
main
Mike Gerwitz 2023-01-31 16:37:25 -05:00
parent 39d093525c
commit f753a23bad
6 changed files with 164 additions and 21 deletions

View File

@ -74,8 +74,8 @@ pub enum Air {
/// Packages are responsible for bundling together identifiers
/// representing subsystems that can be composed with other packages.
///
/// A source language may place limits on the types of [`Object`]s that
/// may appear within a given package,
/// A source language may place limits on the objects that may appear
/// within a given package,
/// but we have no such restriction.
///
/// TODO: The package needs a name,
@ -552,11 +552,12 @@ impl ParseState for AirAggregate {
}
(BuildingExpr(oi_pkg, es, oi), ExprIdent(id)) => {
let identi = asg.lookup_or_missing(id);
let oi_ident = asg.lookup_or_missing(id);
oi_pkg.defines(asg, oi_ident);
// It is important that we do not mark this expression as
// reachable unless we successfully bind the identifier.
match identi.bind_definition(asg, oi) {
match oi_ident.bind_definition(asg, oi) {
Ok(_) => Transition(BuildingExpr(
oi_pkg,
es.reachable_by(id),

View File

@ -1046,6 +1046,36 @@ fn expr_ref_outside_of_expr_context() {
assert_eq!(expr.span(), S1.merge(S3).unwrap());
}
#[test]
fn idents_share_defining_pkg() {
let id_foo = SPair("foo".into(), S2);
let id_bar = SPair("bar".into(), S4);
let id_baz = SPair("baz".into(), S5);
// An expression nested within another.
let toks = vec![
Air::ExprOpen(ExprOp::Sum, S1),
Air::ExprIdent(id_foo),
Air::ExprOpen(ExprOp::Sum, S3),
Air::ExprIdent(id_bar),
Air::ExprRef(id_baz),
Air::ExprClose(S6),
Air::ExprClose(S7),
];
let asg = asg_from_toks(toks);
let oi_foo = asg.lookup(id_foo).unwrap();
let oi_bar = asg.lookup(id_bar).unwrap();
assert_eq!(oi_foo.src_pkg(&asg).unwrap(), oi_bar.src_pkg(&asg).unwrap());
// Missing identifiers should not have a source package,
// since we don't know what defined it yet.
let oi_baz = asg.lookup(id_baz).unwrap();
assert_eq!(None, oi_baz.src_pkg(&asg));
}
fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
where
I::IntoIter: Debug,

View File

@ -19,7 +19,7 @@
//! Abstract semantic graph.
use self::object::{ObjectRelTy, ObjectRelatable};
use self::object::{ObjectRelFrom, ObjectRelTy, ObjectRelatable};
use super::{
AsgError, FragmentText, Ident, IdentKind, Object, ObjectIndex, ObjectKind,
@ -53,8 +53,13 @@ pub trait IndexType = petgraph::graph::IndexType;
/// fail in error.
pub type AsgResult<T> = Result<T, AsgError>;
/// There are currently no data stored on edges ("edge weights").
type AsgEdge = ObjectRelTy;
/// The [`ObjectRelTy`] (representing the [`ObjectKind`]) of the source and
/// destination [`Node`]s respectively.
///
/// This small memory expense allows for bidirectional edge filtering
/// and [`ObjectIndex`] [`ObjectKind`] resolution without an extra layer
/// of indirection to look up the source/target [`Node`].
type AsgEdge = (ObjectRelTy, ObjectRelTy);
/// Each node of the graph.
type Node = ObjectContainer;
@ -289,8 +294,11 @@ impl Asg {
///
/// See also [`IdentKind::is_auto_root`].
pub fn add_root(&mut self, identi: ObjectIndex<Ident>) {
self.graph
.add_edge(self.root_node, identi.into(), ObjectRelTy::Ident);
self.graph.add_edge(
self.root_node,
identi.into(),
(ObjectRelTy::Root, ObjectRelTy::Ident),
);
}
/// Whether an object is rooted.
@ -413,8 +421,11 @@ impl Asg {
) where
OA: ObjectRelTo<OB>,
{
self.graph
.add_edge(from_oi.into(), to_oi.into(), OB::rel_ty());
self.graph.add_edge(
from_oi.into(),
to_oi.into(),
(OA::rel_ty(), OB::rel_ty()),
);
}
/// Retrieve an object from the graph by [`ObjectIndex`].
@ -499,7 +510,7 @@ impl Asg {
) -> impl Iterator<Item = O::Rel> + 'a {
self.graph.edges(oi.into()).map(move |edge| {
O::new_rel_dyn(
*edge.weight(),
edge.weight().1,
ObjectIndex::<Object>::new(edge.target(), oi),
)
.diagnostic_unwrap(|| {
@ -517,6 +528,23 @@ impl Asg {
})
}
/// Incoming edges to `oi` filtered by [`ObjectKind`] `OI`.
///
/// The rationale behind the filtering is that objects ought to focus
/// primarily on what they _relate to_,
/// which is what the ontology is designed around.
/// If an object cares about what has an edge _to_ it,
/// it should have good reason and a specific use case in mind.
fn incoming_edges_filtered<'a, OI: ObjectKind + ObjectRelatable + 'a>(
&'a self,
oi: ObjectIndex<impl ObjectKind + ObjectRelFrom<OI> + 'a>,
) -> impl Iterator<Item = ObjectIndex<OI>> + 'a {
self.graph
.edges_directed(oi.into(), Direction::Incoming)
.filter(|edge| edge.weight().0 == OI::rel_ty())
.map(move |edge| ObjectIndex::<OI>::new(edge.source(), oi))
}
/// Retrieve the [`ObjectIndex`] to which the given `ident` is bound,
/// if any.
///
@ -670,8 +698,11 @@ impl Asg {
) where
Ident: ObjectRelTo<O>,
{
self.graph
.update_edge(identi.into(), depi.into(), O::rel_ty());
self.graph.update_edge(
identi.into(),
depi.into(),
(Ident::rel_ty(), O::rel_ty()),
);
}
/// Check whether `dep` is a dependency of `ident`.
@ -705,8 +736,11 @@ impl Asg {
let identi = self.lookup_or_missing(ident);
let depi = self.lookup_or_missing(dep);
self.graph
.update_edge(identi.into(), depi.into(), Ident::rel_ty());
self.graph.update_edge(
identi.into(),
depi.into(),
(Ident::rel_ty(), Ident::rel_ty()),
);
(identi, depi)
}

View File

@ -150,9 +150,7 @@ pub enum Object {
Expr(Expr),
}
/// Object types corresponding to variants in [`Object`] that are able to
/// serve as targets of object relations
/// (edges on the graph).
/// Object types corresponding to variants in [`Object`].
///
/// These are used as small tags for [`ObjectRelatable`].
/// Rust unfortunately makes working with its internal tags difficult,
@ -162,6 +160,7 @@ pub enum Object {
/// TODO: `pub(super)` when the graph can be better encapsulated.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ObjectRelTy {
Root,
Pkg,
Ident,
Expr,
@ -450,6 +449,20 @@ impl<O: ObjectKind> ObjectIndex<O> {
asg.edges(self)
}
/// 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<Item = ObjectIndex<OI>> + 'a
where
O: ObjectRelFrom<OI> + 'a,
{
asg.incoming_edges_filtered(self)
}
/// Resolve `self` to the object that it references.
///
/// Panics
@ -600,6 +613,9 @@ impl<O: ObjectKind> From<ObjectIndex<O>> for Span {
pub trait ObjectRelTo<OB: ObjectKind + ObjectRelatable> =
ObjectRelatable where <Self as ObjectRelatable>::Rel: From<ObjectIndex<OB>>;
pub(super) trait ObjectRelFrom<OA: ObjectKind + ObjectRelatable> =
ObjectRelatable where <OA as ObjectRelatable>::Rel: From<ObjectIndex<Self>>;
/// Identify [`Self::Rel`] as a sum type consisting of the subset of
/// [`Object`] variants representing the valid _target_ edges of
/// [`Self`].
@ -723,6 +739,7 @@ impl ObjectRelatable for Ident {
oi: ObjectIndex<Object>,
) -> Option<IdentRel> {
match ty {
ObjectRelTy::Root => None,
ObjectRelTy::Ident => Some(IdentRel::Ident(oi.must_narrow_into())),
ObjectRelTy::Expr => Some(IdentRel::Expr(oi.must_narrow_into())),
ObjectRelTy::Pkg => None,
@ -775,6 +792,7 @@ impl ObjectRelatable for Expr {
oi: ObjectIndex<Object>,
) -> Option<ExprRel> {
match ty {
ObjectRelTy::Root => None,
ObjectRelTy::Ident => Some(ExprRel::Ident(oi.must_narrow_into())),
ObjectRelTy::Expr => Some(ExprRel::Expr(oi.must_narrow_into())),
ObjectRelTy::Pkg => None,

View File

@ -21,7 +21,7 @@
use super::{
super::{Asg, AsgError, ObjectIndex, ObjectKind},
ObjectRel, ObjectRelTo, ObjectRelatable,
ObjectRel, ObjectRelTo, ObjectRelatable, Pkg,
};
use crate::{
diagnose::{Annotate, Diagnostic},
@ -1032,6 +1032,12 @@ impl ObjectIndex<Ident> {
) -> bool {
self.edges(asg).find_map(ObjectRel::narrow) == Some(oi)
}
/// The source package defining this identifier,
/// if known.
pub fn src_pkg(&self, asg: &Asg) -> Option<ObjectIndex<Pkg>> {
self.incoming_edges_filtered(asg).next()
}
}
#[cfg(test)]

View File

@ -21,7 +21,12 @@
use std::fmt::Display;
use crate::span::Span;
use crate::{asg::Asg, span::Span};
use super::{
Ident, Object, ObjectIndex, ObjectKind, ObjectRel, ObjectRelTy,
ObjectRelatable,
};
#[derive(Debug, PartialEq, Eq)]
pub struct Pkg(Span);
@ -43,3 +48,52 @@ impl Display for Pkg {
write!(f, "package")
}
}
/// 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 for PkgRel {
fn narrow<OB: ObjectKind + ObjectRelatable>(
self,
) -> Option<ObjectIndex<OB>> {
match self {
Self::Ident(oi) => oi.filter_rel(),
}
}
}
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::Ident => Some(PkgRel::Ident(oi.must_narrow_into())),
ObjectRelTy::Expr => None,
ObjectRelTy::Pkg => None,
}
}
}
impl From<ObjectIndex<Ident>> for PkgRel {
fn from(value: ObjectIndex<Ident>) -> Self {
Self::Ident(value)
}
}
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 {
self.add_edge_to(asg, oi)
}
}