tamer: asg::graph::{index_identifier=>index}: Generalize

This may now index _any_ type of object, in preparation for indexing package
import paths.  In practice, this only makes sense (at least currently) for
`Pkg` and `Ident`.

This generalization also applies to `Asg::lookup_or_missing`.

DEV-13162
main
Mike Gerwitz 2023-04-20 14:55:20 -04:00
parent f183600c3a
commit 6f68292df5
6 changed files with 91 additions and 61 deletions

View File

@ -571,7 +571,7 @@ impl AirAggregateCtx {
// TODO: This currently only indexes for the top of the stack,
// but we'll want no-shadow records for the rest of the env.
if let Some(oi) = self.rooting_oi() {
self.asg_mut().index_identifier(oi, name, oi_ident);
self.asg_mut().index(oi, name, oi_ident);
}
oi_ident

View File

@ -22,11 +22,11 @@
//! ![Visualization of ASG ontology](../ontviz.svg)
use self::object::{
DynObjectRel, ObjectIndexRelTo, ObjectIndexToTree, ObjectIndexTreeRelTo,
ObjectRelFrom, ObjectRelTy, ObjectRelatable, Root,
DynObjectRel, NameableMissingObject, ObjectIndexRelTo,
ObjectIndexTreeRelTo, ObjectRelFrom, ObjectRelTy, ObjectRelatable, Root,
};
use super::{AsgError, Ident, Object, ObjectIndex, ObjectKind};
use super::{AsgError, Object, ObjectIndex, ObjectKind};
use crate::{
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
f::Functor,
@ -81,15 +81,6 @@ type Ix = global::ProgSymSize;
///
/// This implementation is currently based on [`petgraph`].
///
/// Identifiers are cached by name for `O(1)` lookup.
/// Since [`SymbolId`][crate::sym::SymbolId] is used for this purpose,
/// the index may contain more entries than nodes and may contain gaps.
///
/// This IR focuses on the definition and manipulation of objects and their
/// dependencies.
/// See [`Ident`]for a summary of valid identifier object state
/// transitions.
///
/// Objects are never deleted from the graph,
/// so [`ObjectIndex`]s will remain valid for the lifetime of the ASG.
///
@ -100,27 +91,26 @@ pub struct Asg {
/// Directed graph on which objects are stored.
pub graph: DiGraph<Node, AsgEdge, Ix>,
/// Scoped map of [`SymbolId`][crate::sym::SymbolId] to node indexes.
/// Edge cache of [`SymbolId`][crate::sym::SymbolId] to
/// [`ObjectIndex`]es.
///
/// This maps a `(SymbolId, NodeIndex)` pair to a node on the graph,
/// if any,
/// such that the provided [`SymbolId`] is part of the environment at
/// the paired [`NodeIndex`].
/// That is:
/// an identifier must be indexed for _each container_ in its
/// environment,
/// which in turn determines the scope of that identifier.
/// This maps a `(SymbolId, NodeIndex)` pair to a node on the graph for
/// a given [`ObjectRelTy`].
/// _This indexing is not automatic_;
/// it must be explicitly performed using [`Self::index`].
///
/// _An index does not imply the existence of an edge._
/// This index serves as a shortcut for finding nodes on a graph,
/// _but makes no claims about the structure of the graph_.
///
/// This allows for `O(1)` lookup of identifiers in the graph relative
/// to the environment of a given node.
/// to a given node.
/// Note that,
/// while we store [`NodeIndex`] internally,
/// the public API encapsulates it within an [`ObjectIndex`].
index: FxHashMap<(SymbolId, ObjectIndexToTree<Ident>), ObjectIndex<Ident>>,
index: FxHashMap<
(ObjectRelTy, SymbolId, ObjectIndex<Object>),
ObjectIndex<Object>,
>,
/// The root node used for reachability analysis and topological
/// sorting.
@ -192,28 +182,35 @@ impl Asg {
self.graph
}
/// Index the provided symbol `name` as representing the identifier
/// `node` in the immediate environment `imm_env`.
/// Index the provided symbol `name` as representing the
/// [`ObjectIndex`] in the immediate environment `imm_env`.
///
/// This index permits `O(1)` identifier lookups.
/// An index does not require the existence of an edge,
/// but an index may only be created if an edge `imm_env->oi` _could_
/// be constructed.
///
/// This index permits `O(1)` object lookups.
/// The term "immediate environment" is meant to convey that this index
/// applies only to the provided `imm_env` node and does not
/// propagate to any other nodes that share this environment.
/// propagate to any other objects that share this environment.
///
/// After an identifier is indexed it is not expected to be reassigned
/// After an object is indexed it is not expected to be re-indexed
/// to another node.
/// Debug builds contain an assertion that will panic in this instance.
pub(super) fn index_identifier<
OS: ObjectIndexTreeRelTo<Ident>,
pub(super) fn index<
O: ObjectRelatable,
OS: ObjectIndexTreeRelTo<O>,
S: Into<SymbolId>,
>(
&mut self,
imm_env: OS,
name: S,
oi: ObjectIndex<Ident>,
oi: ObjectIndex<O>,
) {
let sym = name.into();
let prev = self.index.insert((sym, imm_env.into()), oi);
let prev = self
.index
.insert((O::rel_ty(), sym, imm_env.widen()), oi.widen());
// We should never overwrite indexes
#[allow(unused_variables)] // used only for debug
@ -246,35 +243,34 @@ impl Asg {
}
}
/// Lookup `ident` or add a missing identifier to the graph relative to
/// Lookup `name or add a missing object to the graph relative to
/// the immediate environment `imm_env` and return a reference to it.
///
/// The provided span is necessary to seed the missing identifier with
/// some sort of context to aid in debugging why a missing identifier
/// The provided span is necessary to seed the missing object with
/// some sort of context to aid in debugging why a missing object
/// was introduced to the graph.
/// The provided span will be used even if an identifier exists on the
/// graph,
/// The provided span will be used by the returned [`ObjectIndex`] even
/// if an object exists on the graph,
/// which can be used for retaining information on the location that
/// requested the identifier.
/// To retrieve the span of a previously declared identifier,
/// you must resolve the [`Ident`] object and inspect it.
/// requested the object.
/// To retrieve the span of a previously declared object,
/// you must resolve the [`ObjectIndex`] and inspect it.
///
/// See [`Ident::declare`] for more information.
pub(super) fn lookup_or_missing<OS: ObjectIndexTreeRelTo<Ident>>(
/// See [`Self::index`] for more information.
pub(super) fn lookup_or_missing<
O: ObjectRelatable,
OS: ObjectIndexTreeRelTo<O>,
>(
&mut self,
imm_env: OS,
name: SPair,
) -> ObjectIndex<Ident> {
) -> ObjectIndex<O>
where
O: NameableMissingObject,
{
self.lookup(imm_env, name).unwrap_or_else(|| {
let index = self.graph.add_node(Ident::declare(name).into());
let oi = ObjectIndex::new(index, name.span());
self.index_identifier(
ObjectIndex::<Root>::new(self.root_node, name.span()),
name.symbol(),
oi,
);
let oi = self.create(O::missing(name));
self.index(imm_env, name.symbol(), oi);
oi
})
}
@ -500,14 +496,20 @@ impl Asg {
/// compilation unit,
/// which is a package.
#[inline]
pub fn lookup<OS: ObjectIndexTreeRelTo<Ident>>(
pub fn lookup<O: ObjectRelatable, OS: ObjectIndexTreeRelTo<O>>(
&self,
imm_env: OS,
id: SPair,
) -> Option<ObjectIndex<Ident>> {
) -> Option<ObjectIndex<O>> {
// The type `O` is encoded into the index on [`Self::index`] and so
// should always be able to be narrowed into the expected type.
// If this invariant somehow does not hold,
// then the system will panic when the object is resolved.
// Maybe future Rust will have dependent types that allow for better
// static assurances.
self.index
.get(&(id.symbol(), imm_env.into()))
.map(|&ni| ni.overwrite(id.span()))
.get(&(O::rel_ty(), id.symbol(), imm_env.widen()))
.map(|&ni| ni.overwrite(id.span()).must_narrow_into::<O>())
}
}

View File

@ -205,7 +205,7 @@ macro_rules! object_gen {
///
/// TODO: Encapsulate within `crate::asg` when the graph can be better
/// encapsulated.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ObjectTy {
$($kind,)+
}
@ -963,3 +963,18 @@ fn container_oops() -> Vec<AnnotatedSpan<'static>> {
),
]
}
/// An [`ObjectKind`] with a representation of a missing object associated
/// with a [`SymbolId`](crate::sym::SymbolId).
///
/// A _missing_ object has a node on the graph to which edges may be
/// drawn for when the object is eventually defined.
/// Missing objects are identified by a [`SymbolId`](crate::sym::SymbolId),
/// allowing them to be later retrieved for definition.
pub trait NameableMissingObject: ObjectKind {
/// Represent an object as missing on the [`Asg`].
///
/// The provided `name` will be used to later look up the object to
/// provide a definition.
fn missing(name: SPair) -> Self;
}

View File

@ -19,7 +19,7 @@
//! Identifiers (a type of [object](super)).
use super::{prelude::*, Expr, Meta, Pkg, Tpl};
use super::{prelude::*, Expr, Meta, NameableMissingObject, Pkg, Tpl};
use crate::{
diagnose::{Annotate, Diagnostic},
diagnostic_todo,
@ -169,6 +169,12 @@ impl Display for Ident {
}
}
impl NameableMissingObject for Ident {
fn missing(name: SPair) -> Self {
Self::Missing(name)
}
}
impl Ident {
/// Identifier name.
pub fn name(&self) -> SPair {

View File

@ -19,7 +19,7 @@
//! Package object on the ASG.
use super::{prelude::*, Doc, Ident, Tpl};
use super::{prelude::*, Doc, Ident, NameableMissingObject, Tpl};
use crate::{
f::Functor,
parse::{util::SPair, Token},
@ -72,6 +72,12 @@ impl Display for Pkg {
}
}
impl NameableMissingObject for Pkg {
fn missing(pathspec: SPair) -> Self {
Self::new_imported(pathspec)
}
}
impl Functor<Span> for Pkg {
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
match self {

View File

@ -19,6 +19,7 @@
use super::*;
use crate::span::dummy::*;
use object::Ident;
use std::convert::Infallible;
type Sut = Asg;