820 lines
29 KiB
Rust
820 lines
29 KiB
Rust
// Graph abstraction
|
||
//
|
||
// 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/>.
|
||
|
||
//! Abstract semantic graph.
|
||
|
||
use self::object::{
|
||
DynObjectRel, ObjectRelFrom, ObjectRelTy, ObjectRelatable, Root,
|
||
};
|
||
|
||
use super::{
|
||
AsgError, FragmentText, Ident, IdentKind, Object, ObjectIndex, ObjectKind,
|
||
Source, TransitionResult,
|
||
};
|
||
use crate::{
|
||
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
||
f::Functor,
|
||
fmt::{DisplayWrapper, TtQuote},
|
||
global,
|
||
parse::{util::SPair, Token},
|
||
span::Span,
|
||
sym::SymbolId,
|
||
};
|
||
use petgraph::{
|
||
graph::{DiGraph, Graph, NodeIndex},
|
||
visit::EdgeRef,
|
||
Direction,
|
||
};
|
||
use std::{fmt::Debug, result::Result};
|
||
|
||
pub mod object;
|
||
pub mod visit;
|
||
|
||
use object::{ObjectContainer, ObjectRelTo};
|
||
|
||
/// Datatype representing node and edge indexes.
|
||
pub trait IndexType = petgraph::graph::IndexType;
|
||
|
||
/// A [`Result`] with a hard-coded [`AsgError`] error type.
|
||
///
|
||
/// This is the result of every [`Asg`] operation that could potentially
|
||
/// fail in error.
|
||
pub type AsgResult<T> = Result<T, AsgError>;
|
||
|
||
/// 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`].
|
||
///
|
||
/// The edge may also optionally contain a [`Span`] that provides additional
|
||
/// context in situations where the distinction between the span of the
|
||
/// target object and the span of the _reference_ to that object is
|
||
/// important.
|
||
type AsgEdge = (ObjectRelTy, ObjectRelTy, Option<Span>);
|
||
|
||
/// Each node of the graph.
|
||
type Node = ObjectContainer;
|
||
|
||
/// Index size for Graph nodes and edges.
|
||
type Ix = global::ProgSymSize;
|
||
|
||
/// An abstract semantic graph (ASG) of [objects](object).
|
||
///
|
||
/// 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.
|
||
///
|
||
/// For more information,
|
||
/// see the [module-level documentation][self].
|
||
pub struct Asg {
|
||
// TODO: private; see `ld::xmle::lower`.
|
||
/// Directed graph on which objects are stored.
|
||
pub graph: DiGraph<Node, AsgEdge, Ix>,
|
||
|
||
/// Map of [`SymbolId`][crate::sym::SymbolId] to node indexes.
|
||
///
|
||
/// This allows for `O(1)` lookup of identifiers in the graph.
|
||
/// Note that,
|
||
/// while we store [`NodeIndex`] internally,
|
||
/// the public API encapsulates it within an [`ObjectIndex`].
|
||
index: Vec<NodeIndex<Ix>>,
|
||
|
||
/// Empty node indicating that no object exists for a given index.
|
||
empty_node: NodeIndex<Ix>,
|
||
|
||
/// The root node used for reachability analysis and topological
|
||
/// sorting.
|
||
root_node: NodeIndex<Ix>,
|
||
}
|
||
|
||
impl Debug for Asg {
|
||
/// Trimmed-down Asg [`Debug`] output.
|
||
///
|
||
/// This primarily hides the large `self.index` that takes up so much
|
||
/// space in parser traces,
|
||
/// but also hides irrelevant information.
|
||
///
|
||
/// The better option in the future may be to create a newtype for
|
||
/// `index` if it sticks around in its current form,
|
||
/// which in turn can encapsulate `self.empty_node`.
|
||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||
f.debug_struct("Asg")
|
||
.field("root_node", &self.root_node)
|
||
.field("graph", &self.graph)
|
||
.finish_non_exhaustive()
|
||
}
|
||
}
|
||
|
||
impl Default for Asg {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl Asg {
|
||
/// Create a new ASG.
|
||
///
|
||
/// See also [`with_capacity`](Asg::with_capacity).
|
||
pub fn new() -> Self {
|
||
// TODO: Determine a proper initial capacity.
|
||
Self::with_capacity(0, 0)
|
||
}
|
||
|
||
/// Create an ASG with the provided initial capacity.
|
||
///
|
||
/// The value for `objects` will be used as the capacity for the nodes
|
||
/// in the graph,
|
||
/// as well as the initial index capacity.
|
||
/// The value for `edges` may be more difficult to consider,
|
||
/// since edges are used to represent various relationships between
|
||
/// different types of objects,
|
||
/// but it's safe to say that each object will have at least one
|
||
/// edge to another object.
|
||
pub fn with_capacity(objects: usize, edges: usize) -> Self {
|
||
let mut graph = Graph::with_capacity(objects, edges);
|
||
let mut index = Vec::with_capacity(objects);
|
||
|
||
// Exhaust the first index to be used as a placeholder
|
||
// (its value does not matter).
|
||
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(Root).into());
|
||
|
||
Self {
|
||
graph,
|
||
index,
|
||
empty_node,
|
||
root_node,
|
||
}
|
||
}
|
||
|
||
/// Get the underlying Graph
|
||
pub fn into_inner(self) -> DiGraph<Node, AsgEdge, Ix> {
|
||
self.graph
|
||
}
|
||
|
||
/// Index the provided symbol `name` as representing the identifier `node`.
|
||
///
|
||
/// This index permits `O(1)` identifier lookups.
|
||
///
|
||
/// After an identifier is indexed it is not expected to be reassigned
|
||
/// to another node.
|
||
/// Debug builds contain an assertion that will panic in this instance.
|
||
///
|
||
/// Panics
|
||
/// ======
|
||
/// Will panic if unable to allocate more space for the index.
|
||
fn index_identifier(&mut self, name: SymbolId, node: NodeIndex<Ix>) {
|
||
let i = name.as_usize();
|
||
|
||
if i >= self.index.len() {
|
||
// If this is ever a problem we can fall back to usize max and
|
||
// re-compare before panicing
|
||
let new_size = (i + 1)
|
||
.checked_next_power_of_two()
|
||
.expect("internal error: cannot allocate space for ASG index");
|
||
|
||
self.index.resize(new_size, self.empty_node);
|
||
}
|
||
|
||
// We should never overwrite indexes
|
||
debug_assert!(self.index[i] == self.empty_node);
|
||
|
||
self.index[i] = node;
|
||
}
|
||
|
||
/// Lookup `ident` or add a missing identifier to the graph 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
|
||
/// was introduced to the graph.
|
||
/// The provided span will be used even if an identifier 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.
|
||
///
|
||
/// See [`Ident::declare`] for more information.
|
||
pub(super) fn lookup_or_missing(
|
||
&mut self,
|
||
ident: SPair,
|
||
) -> ObjectIndex<Ident> {
|
||
self.lookup(ident).unwrap_or_else(|| {
|
||
let index = self.graph.add_node(Ident::declare(ident).into());
|
||
|
||
self.index_identifier(ident.symbol(), index);
|
||
ObjectIndex::new(index, ident.span())
|
||
})
|
||
}
|
||
|
||
/// Perform a state transition on an identifier by name.
|
||
///
|
||
/// Look up `ident` or add a missing identifier if it does not yet exist
|
||
/// (see [`Self::lookup_or_missing`]).
|
||
/// Then invoke `f` with the located identifier and replace the
|
||
/// identifier on the graph with the result.
|
||
///
|
||
/// This will safely restore graph state to the original identifier
|
||
/// value on transition failure.
|
||
fn with_ident_lookup<F>(
|
||
&mut self,
|
||
name: SPair,
|
||
f: F,
|
||
) -> AsgResult<ObjectIndex<Ident>>
|
||
where
|
||
F: FnOnce(Ident) -> TransitionResult<Ident>,
|
||
{
|
||
let identi = self.lookup_or_missing(name);
|
||
self.with_ident(identi, f)
|
||
}
|
||
|
||
/// Perform a state transition on an identifier by [`ObjectIndex`].
|
||
///
|
||
/// Invoke `f` with the located identifier and replace the identifier on
|
||
/// the graph with the result.
|
||
///
|
||
/// This will safely restore graph state to the original identifier
|
||
/// value on transition failure.
|
||
fn with_ident<F>(
|
||
&mut self,
|
||
identi: ObjectIndex<Ident>,
|
||
f: F,
|
||
) -> AsgResult<ObjectIndex<Ident>>
|
||
where
|
||
F: FnOnce(Ident) -> TransitionResult<Ident>,
|
||
{
|
||
let container = self.graph.node_weight_mut(identi.into()).unwrap();
|
||
|
||
container
|
||
.try_replace_with(f)
|
||
.map(|()| identi)
|
||
.map_err(Into::into)
|
||
}
|
||
|
||
/// 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.
|
||
///
|
||
/// Roots are always included during a topological sort and any
|
||
/// reachability analysis.
|
||
///
|
||
/// Ideally,
|
||
/// roots would be minimal and dependencies properly organized such
|
||
/// that objects will be included if they are a transitive dependency
|
||
/// of some included subsystem.
|
||
///
|
||
/// 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::Root, ObjectRelTy::Ident, None),
|
||
);
|
||
}
|
||
|
||
/// Whether an object is rooted.
|
||
///
|
||
/// See [`Asg::add_root`] for more information about roots.
|
||
#[cfg(test)]
|
||
pub(super) fn is_rooted(&self, identi: ObjectIndex<Ident>) -> bool {
|
||
self.graph.contains_edge(self.root_node, identi.into())
|
||
}
|
||
|
||
/// Declare a concrete identifier.
|
||
///
|
||
/// An identifier declaration is similar to a declaration in a header
|
||
/// file in a language like C,
|
||
/// describing the structure of the identifier.
|
||
/// Once declared,
|
||
/// this information cannot be changed.
|
||
///
|
||
/// Identifiers are uniquely identified by a [`SymbolId`] `name`.
|
||
/// If an identifier of the same `name` already exists,
|
||
/// then the provided declaration is compared against the existing
|
||
/// declaration---should
|
||
/// they be incompatible,
|
||
/// then the operation will fail;
|
||
/// otherwise,
|
||
/// the existing identifier will be returned.
|
||
///
|
||
/// If a concrete identifier has already been declared (see
|
||
/// [`Asg::declare`]),
|
||
/// then extern declarations will be compared and,
|
||
/// if compatible,
|
||
/// the identifier will be immediately _resolved_ and the object
|
||
/// on the graph will not be altered.
|
||
/// Resolution will otherwise fail in error.
|
||
///
|
||
/// For more information on state transitions that can occur when
|
||
/// redeclaring an identifier that already exists,
|
||
/// see [`Ident::resolve`].
|
||
///
|
||
/// A successful declaration will add an identifier to the graph
|
||
/// and return an [`ObjectIndex`] reference.
|
||
pub fn declare(
|
||
&mut self,
|
||
name: SPair,
|
||
kind: IdentKind,
|
||
src: Source,
|
||
) -> AsgResult<ObjectIndex<Ident>> {
|
||
let is_auto_root = kind.is_auto_root();
|
||
|
||
self.with_ident_lookup(name, |obj| obj.resolve(name.span(), kind, src))
|
||
.map(|node| {
|
||
is_auto_root.then(|| self.add_root(node));
|
||
node
|
||
})
|
||
}
|
||
|
||
/// Declare an abstract identifier.
|
||
///
|
||
/// An _extern_ declaration declares an identifier the same as
|
||
/// [`Asg::declare`],
|
||
/// but omits source information.
|
||
/// Externs are identifiers that are expected to be defined somewhere
|
||
/// else ("externally"),
|
||
/// and are resolved at [link-time][crate::ld].
|
||
///
|
||
/// If a concrete identifier has already been declared (see
|
||
/// [`Asg::declare`]),
|
||
/// then the declarations will be compared and,
|
||
/// if compatible,
|
||
/// the identifier will be immediately _resolved_ and the object
|
||
/// on the graph will not be altered.
|
||
/// Resolution will otherwise fail in error.
|
||
///
|
||
/// See [`Ident::extern_`] and
|
||
/// [`Ident::resolve`] for more information on
|
||
/// compatibility related to extern resolution.
|
||
pub fn declare_extern(
|
||
&mut self,
|
||
name: SPair,
|
||
kind: IdentKind,
|
||
src: Source,
|
||
) -> AsgResult<ObjectIndex<Ident>> {
|
||
self.with_ident_lookup(name, |obj| obj.extern_(name.span(), kind, src))
|
||
}
|
||
|
||
/// Set the fragment associated with a concrete identifier.
|
||
///
|
||
/// Fragments are intended for use by the [linker][crate::ld].
|
||
/// For more information,
|
||
/// see [`Ident::set_fragment`].
|
||
pub fn set_fragment(
|
||
&mut self,
|
||
name: SPair,
|
||
text: FragmentText,
|
||
) -> AsgResult<ObjectIndex<Ident>> {
|
||
self.with_ident_lookup(name, |obj| obj.set_fragment(text))
|
||
}
|
||
|
||
/// Create a new object on the graph.
|
||
///
|
||
/// The provided [`ObjectIndex`] will be augmented with the span
|
||
/// of `obj`.
|
||
pub(super) fn create<O: ObjectKind>(&mut self, obj: O) -> ObjectIndex<O> {
|
||
let o = obj.into();
|
||
let span = o.span();
|
||
let node_id = self.graph.add_node(ObjectContainer::from(o.into()));
|
||
|
||
ObjectIndex::new(node_id, span)
|
||
}
|
||
|
||
/// Add an edge from the [`Object`] represented by the
|
||
/// [`ObjectIndex`] `from_oi` to the object represented by `to_oi`.
|
||
///
|
||
/// The edge may optionally contain a _contextual [`Span`]_,
|
||
/// in cases where it is important to distinguish between the span
|
||
/// associated with the target and the span associated with the
|
||
/// _reference_ to the target.
|
||
///
|
||
/// For more information on how the ASG's ontology is enforced statically,
|
||
/// see [`ObjectRelTo`].
|
||
fn add_edge<OA: ObjectKind, OB: ObjectKind>(
|
||
&mut self,
|
||
from_oi: ObjectIndex<OA>,
|
||
to_oi: ObjectIndex<OB>,
|
||
ctx_span: Option<Span>,
|
||
) where
|
||
OA: ObjectRelTo<OB>,
|
||
{
|
||
self.graph.add_edge(
|
||
from_oi.into(),
|
||
to_oi.into(),
|
||
(OA::rel_ty(), OB::rel_ty(), ctx_span),
|
||
);
|
||
}
|
||
|
||
/// Retrieve an object from the graph by [`ObjectIndex`].
|
||
///
|
||
/// Since an [`ObjectIndex`] should only be produced by an [`Asg`],
|
||
/// and since objects are never deleted from the graph,
|
||
/// this should never fail so long as references are not shared
|
||
/// between multiple graphs.
|
||
/// It is nevertheless wrapped in an [`Option`] just in case.
|
||
#[inline]
|
||
pub fn get<O: ObjectKind>(&self, index: ObjectIndex<O>) -> Option<&O> {
|
||
self.graph
|
||
.node_weight(index.into())
|
||
.map(ObjectContainer::get)
|
||
}
|
||
|
||
/// Attempt to map over an inner [`Object`] referenced by
|
||
/// [`ObjectIndex`].
|
||
///
|
||
/// The type `O` is the expected type of the [`Object`],
|
||
/// which should be known to the caller based on the provied
|
||
/// [`ObjectIndex`].
|
||
/// This method will attempt to narrow to that object type,
|
||
/// panicing if there is a mismatch;
|
||
/// see the [`object` module documentation](object) for more
|
||
/// information and rationale on this behavior.
|
||
///
|
||
/// Panics
|
||
/// ======
|
||
/// This method chooses to simplify the API by choosing panics for
|
||
/// situations that ought never to occur and represent significant bugs
|
||
/// in the compiler.
|
||
/// Those situations are:
|
||
///
|
||
/// 1. If the provided [`ObjectIndex`] references a node index that is
|
||
/// not present on the graph;
|
||
/// 2. If the node referenced by [`ObjectIndex`] exists but its container
|
||
/// is empty because an object was taken but never returned; and
|
||
/// 3. If an object cannot be narrowed (downcast) to type `O`,
|
||
/// representing a type mismatch between what the caller thinks
|
||
/// this object represents and what the object actually is.
|
||
#[must_use = "returned ObjectIndex has a possibly-updated and more relevant span"]
|
||
pub(super) fn try_map_obj<O: ObjectKind, E>(
|
||
&mut self,
|
||
index: ObjectIndex<O>,
|
||
f: impl FnOnce(O) -> Result<O, (O, E)>,
|
||
) -> Result<ObjectIndex<O>, E> {
|
||
let obj_container =
|
||
self.graph.node_weight_mut(index.into()).diagnostic_expect(
|
||
|| diagnostic_node_missing_desc(index),
|
||
"invalid ObjectIndex: data are missing from the ASG",
|
||
);
|
||
|
||
obj_container
|
||
.try_replace_with(f)
|
||
.map(|()| index.overwrite(obj_container.get::<Object>().span()))
|
||
}
|
||
|
||
/// Create an iterator over the [`ObjectIndex`]es of the outgoing edges
|
||
/// of `oi`.
|
||
///
|
||
/// This is a generic method that simply returns an [`ObjectKind`] of
|
||
/// [`Object`] for each [`ObjectIndex`];
|
||
/// it is the responsibility of the caller to narrow the type to
|
||
/// what is intended.
|
||
/// This is sufficient in practice,
|
||
/// since the graph cannot be constructed without adhering to the edge
|
||
/// ontology defined by [`ObjectRelTo`],
|
||
/// but this API is not helpful for catching problems at
|
||
/// compile-time.
|
||
///
|
||
/// The reason for providing a generic index to [`Object`] is that it
|
||
/// allows the caller to determine how strict it wants to be with
|
||
/// reading from the graph;
|
||
/// for example,
|
||
/// it may prefer to filter unwanted objects rather than panicing
|
||
/// if they do not match a given [`ObjectKind`],
|
||
/// depending on its ontology.
|
||
fn edges<'a, O: ObjectKind + ObjectRelatable + 'a>(
|
||
&'a self,
|
||
oi: ObjectIndex<O>,
|
||
) -> impl Iterator<Item = O::Rel> + 'a {
|
||
self.edges_dyn(oi.widen()).map(move |dyn_rel| {
|
||
let target_ty = dyn_rel.target_ty();
|
||
|
||
dyn_rel.narrow::<O>().diagnostic_unwrap(|| {
|
||
vec![
|
||
oi.internal_error(format!(
|
||
"encountered invalid outgoing edge type {:?}",
|
||
target_ty,
|
||
)),
|
||
oi.help(
|
||
"this means that Asg did not enforce edge invariants \
|
||
during construction, which is a significant bug",
|
||
),
|
||
]
|
||
})
|
||
})
|
||
}
|
||
|
||
/// Create an iterator over the [`ObjectIndex`]es of the outgoing edges
|
||
/// of `oi` in a dynamic context.
|
||
///
|
||
/// _This method should be used only when the types of objects cannot be
|
||
/// statically known,_
|
||
/// which is generally true only for code paths operating on
|
||
/// significant portions of
|
||
/// (or the entirety of)
|
||
/// the graph without distinction.
|
||
/// See [`Self::edges`] for more information.
|
||
fn edges_dyn<'a>(
|
||
&'a self,
|
||
oi: ObjectIndex<Object>,
|
||
) -> impl Iterator<Item = DynObjectRel> + 'a {
|
||
self.graph.edges(oi.into()).map(move |edge| {
|
||
let (src_ty, target_ty, ctx_span) = edge.weight();
|
||
|
||
DynObjectRel::new(
|
||
*src_ty,
|
||
*target_ty,
|
||
ObjectIndex::<Object>::new(edge.target(), oi),
|
||
*ctx_span,
|
||
)
|
||
})
|
||
}
|
||
|
||
/// 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.
|
||
///
|
||
/// The type parameter `O` indicates the _expected_ [`ObjectKind`] to be
|
||
/// bound to the returned [`ObjectIndex`],
|
||
/// which will be used for narrowing (downcasting) the object after
|
||
/// lookup.
|
||
/// An incorrect kind will not cause any failures until such a lookup
|
||
/// occurs.
|
||
///
|
||
/// This will return [`None`] if the identifier is either opaque or does
|
||
/// not exist.
|
||
fn get_ident_oi<O: ObjectKind>(
|
||
&self,
|
||
ident: SPair,
|
||
) -> Option<ObjectIndex<O>> {
|
||
self.lookup(ident)
|
||
.and_then(|identi| {
|
||
self.graph
|
||
.neighbors_directed(identi.into(), Direction::Outgoing)
|
||
.next()
|
||
})
|
||
// Note that this use of `O` for `ObjectIndex` here means "I
|
||
// _expect_ this to `O`";
|
||
// the type will be verified during narrowing but will panic
|
||
// if this expectation is not met.
|
||
.map(|ni| ObjectIndex::<O>::new(ni, ident.span()))
|
||
}
|
||
|
||
/// Retrieve the [`ObjectIndex`] to which the given `ident` is bound,
|
||
/// panicing if the identifier is either opaque or does not exist.
|
||
///
|
||
/// Panics
|
||
/// ======
|
||
/// This method will panic if the identifier is opaque
|
||
/// (has no edge to the object to which it is bound)
|
||
/// or does not exist on the graph.
|
||
pub fn expect_ident_oi<O: ObjectKind>(
|
||
&self,
|
||
ident: SPair,
|
||
) -> ObjectIndex<O> {
|
||
self.get_ident_oi(ident).diagnostic_expect(
|
||
|| diagnostic_opaque_ident_desc(ident),
|
||
|| {
|
||
format!(
|
||
"opaque identifier: {} has no object binding",
|
||
TtQuote::wrap(ident),
|
||
)
|
||
},
|
||
)
|
||
}
|
||
|
||
/// Attempt to retrieve the [`Object`] to which the given `ident` is bound.
|
||
///
|
||
/// If the identifier either does not exist on the graph or is opaque
|
||
/// (is not bound to any expression),
|
||
/// then [`None`] will be returned.
|
||
///
|
||
/// If the system expects that the identifier must exist and would
|
||
/// otherwise represent a bug in the compiler,
|
||
/// see [`Self::expect_ident_obj`].
|
||
///
|
||
/// Panics
|
||
/// ======
|
||
/// This method will panic if certain graph invariants are not met,
|
||
/// representing an invalid system state that should not be able to
|
||
/// occur through this API.
|
||
/// Violations of these invariants represent either a bug in the API
|
||
/// (that allows for the invariant to be violated)
|
||
/// or direct manipulation of the underlying graph.
|
||
pub fn get_ident_obj<O: ObjectKind>(&self, ident: SPair) -> Option<&O> {
|
||
self.get_ident_oi::<O>(ident).map(|oi| self.expect_obj(oi))
|
||
}
|
||
|
||
pub(super) fn expect_obj<O: ObjectKind>(&self, oi: ObjectIndex<O>) -> &O {
|
||
let obj_container =
|
||
self.graph.node_weight(oi.into()).diagnostic_expect(
|
||
|| diagnostic_node_missing_desc(oi),
|
||
"invalid ObjectIndex: data are missing from the ASG",
|
||
);
|
||
|
||
obj_container.get()
|
||
}
|
||
|
||
/// Attempt to retrieve the [`Object`] to which the given `ident` is bound,
|
||
/// panicing if the identifier is opaque or does not exist.
|
||
///
|
||
/// This method represents a compiler invariant;
|
||
/// it should _only_ be used when the identifier _must_ exist,
|
||
/// otherwise there is a bug in the compiler.
|
||
/// If this is _not_ the case,
|
||
/// use [`Self::get_ident_obj`] to get [`None`] in place of a panic.
|
||
///
|
||
/// Panics
|
||
/// ======
|
||
/// This method will panic if
|
||
///
|
||
/// 1. The identifier does not exist on the graph; or
|
||
/// 2. The identifier is opaque (has no edge to any object on the
|
||
/// graph).
|
||
pub fn expect_ident_obj<O: ObjectKind>(&self, ident: SPair) -> &O {
|
||
self.get_ident_obj(ident).diagnostic_expect(
|
||
|| diagnostic_opaque_ident_desc(ident),
|
||
|| {
|
||
format!(
|
||
"opaque identifier: {} has no object binding",
|
||
TtQuote::wrap(ident),
|
||
)
|
||
},
|
||
)
|
||
}
|
||
|
||
/// Retrieve an identifier from the graph by [`ObjectIndex`].
|
||
///
|
||
/// If the object exists but is not an identifier,
|
||
/// [`None`] will be returned.
|
||
#[inline]
|
||
pub fn get_ident(&self, index: ObjectIndex<Ident>) -> Option<&Ident> {
|
||
self.get(index)
|
||
}
|
||
|
||
/// Attempt to retrieve an identifier from the graph by name.
|
||
///
|
||
/// Since only identifiers carry a name,
|
||
/// this method cannot be used to retrieve all possible objects on the
|
||
/// graph---for
|
||
/// that, see [`Asg::get`].
|
||
#[inline]
|
||
pub fn lookup(&self, id: SPair) -> Option<ObjectIndex<Ident>> {
|
||
let i = id.symbol().as_usize();
|
||
|
||
self.index
|
||
.get(i)
|
||
.filter(|ni| ni.index() > 0)
|
||
.map(|ni| ObjectIndex::new(*ni, id.span()))
|
||
}
|
||
|
||
/// Declare that `dep` is a dependency of `ident`.
|
||
///
|
||
/// An object must be declared as a dependency if its value must be
|
||
/// computed before computing the value of `ident`.
|
||
/// The [linker][crate::ld] will ensure this ordering.
|
||
///
|
||
/// See [`add_dep_lookup`][Asg::add_dep_lookup] if identifiers have to
|
||
/// be looked up by [`SymbolId`] or if they may not yet have been
|
||
/// declared.
|
||
pub fn add_dep<O: ObjectKind>(
|
||
&mut self,
|
||
identi: ObjectIndex<Ident>,
|
||
depi: ObjectIndex<O>,
|
||
) where
|
||
Ident: ObjectRelTo<O>,
|
||
{
|
||
self.graph.update_edge(
|
||
identi.into(),
|
||
depi.into(),
|
||
(Ident::rel_ty(), O::rel_ty(), None),
|
||
);
|
||
}
|
||
|
||
/// Check whether `dep` is a dependency of `ident`.
|
||
#[inline]
|
||
pub fn has_dep(
|
||
&self,
|
||
ident: ObjectIndex<Ident>,
|
||
dep: ObjectIndex<Ident>,
|
||
) -> bool {
|
||
self.graph.contains_edge(ident.into(), dep.into())
|
||
}
|
||
|
||
/// Declare that `dep` is a dependency of `ident`,
|
||
/// regardless of whether they are known.
|
||
///
|
||
/// In contrast to [`add_dep`][Asg::add_dep],
|
||
/// this method will add the dependency even if one or both of `ident`
|
||
/// or `dep` have not yet been declared.
|
||
/// In such a case,
|
||
/// a missing identifier will be added as a placeholder,
|
||
/// allowing the ASG to be built with partial information as
|
||
/// identifiers continue to be discovered.
|
||
/// See [`Ident::declare`] for more information.
|
||
///
|
||
/// References to both identifiers are returned in argument order.
|
||
pub fn add_dep_lookup(
|
||
&mut self,
|
||
ident: SPair,
|
||
dep: SPair,
|
||
) -> (ObjectIndex<Ident>, ObjectIndex<Ident>) {
|
||
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(), Ident::rel_ty(), None),
|
||
);
|
||
|
||
(identi, depi)
|
||
}
|
||
}
|
||
|
||
fn diagnostic_node_missing_desc<O: ObjectKind>(
|
||
index: ObjectIndex<O>,
|
||
) -> Vec<AnnotatedSpan<'static>> {
|
||
vec![
|
||
index.internal_error("this object is missing from the ASG"),
|
||
index.help("this means that either an ObjectIndex was malformed, or"),
|
||
index.help(" the object no longer exists on the graph, both of"),
|
||
index.help(" which are unexpected and possibly represent data"),
|
||
index.help(" corruption."),
|
||
index.help("The system cannot proceed with confidence."),
|
||
]
|
||
}
|
||
|
||
fn diagnostic_opaque_ident_desc(ident: SPair) -> Vec<AnnotatedSpan<'static>> {
|
||
vec![
|
||
ident.internal_error(
|
||
"this identifier is not bound to any object on the ASG",
|
||
),
|
||
ident.help("the system expects to be able to reach the object that"),
|
||
ident.help(" this identifies, but this identifier has no"),
|
||
ident.help(" corresponding object present on the graph."),
|
||
]
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod test;
|