// Graph abstraction
//
// Copyright (C) 2014-2022 Ryan Specialty Group, 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 .
//! Abstract graph as the basis for concrete ASGs.
use super::{
AsgError, FragmentText, Ident, IdentKind, Object, ObjectIndex, ObjectKind,
Source, TransitionResult,
};
use crate::diagnose::panic::DiagnosticPanic;
use crate::diagnose::Annotate;
use crate::global;
use crate::parse::util::SPair;
use crate::parse::Token;
use crate::span::UNKNOWN_SPAN;
use crate::sym::SymbolId;
use petgraph::graph::{DiGraph, Graph, NodeIndex};
use std::fmt::Debug;
use std::result::Result;
/// 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 = Result;
/// There are currently no data stored on edges ("edge weights").
pub type AsgEdge = ();
/// Each node of the graph represents an object.
///
/// Enclosed in an [`Option`] to permit moving owned values out of the
/// graph.
pub type Node = Option;
/// Index size for Graph nodes and edges.
type Ix = global::ProgSymSize;
/// An abstract semantic graph (ASG) of [objects][super::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].
#[derive(Debug)]
pub struct Asg {
// TODO: private; see `ld::xmle::lower`.
/// Directed graph on which objects are stored.
pub graph: DiGraph,
/// 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>,
/// Empty node indicating that no object exists for a given index.
empty_node: NodeIndex,
/// The root node used for reachability analysis and topological
/// sorting.
root_node: NodeIndex,
}
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.
let empty_node = graph.add_node(None);
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(Some(Object::Root));
Self {
graph,
index,
empty_node,
root_node,
}
}
/// Get the underlying Graph
pub fn into_inner(self) -> DiGraph {
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) {
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.
///
/// See [`Ident::declare`] for more information.
pub(super) fn lookup_or_missing(
&mut self,
ident: SPair,
) -> ObjectIndex {
let sym = ident.symbol();
self.lookup(sym).unwrap_or_else(|| {
let index = self.graph.add_node(Some(Ident::declare(ident).into()));
self.index_identifier(sym, 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(
&mut self,
name: SPair,
f: F,
) -> AsgResult>
where
F: FnOnce(Ident) -> TransitionResult,
{
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(
&mut self,
identi: ObjectIndex,
f: F,
) -> AsgResult>
where
F: FnOnce(Ident) -> TransitionResult,
{
let node = self.graph.node_weight_mut(identi.into()).unwrap();
let obj = node
.take()
.expect("internal error: missing object")
.unwrap_ident();
f(obj)
.and_then(|obj| {
node.replace(obj.into());
Ok(identi)
})
.or_else(|(orig, err)| {
node.replace(orig.into());
Err(err.into())
})
}
// TODO: This is transitional;
// remove once [`crate::xmlo::asg_builder`] is removed.
pub fn root(&self) -> NodeIndex {
self.root_node
}
/// 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) {
self.graph
.add_edge(self.root_node, identi.into(), Default::default());
}
/// Whether an object is rooted.
///
/// See [`Asg::add_root`] for more information about roots.
#[cfg(test)]
pub(super) fn is_rooted(&self, identi: ObjectIndex) -> 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> {
let is_auto_root = kind.is_auto_root();
self.with_ident_lookup(name, |obj| obj.resolve(name.span(), kind, src))
.and_then(|node| {
is_auto_root.then(|| self.add_root(node));
Ok(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> {
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> {
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(&mut self, obj: O) -> ObjectIndex {
let o = obj.into();
let span = o.span();
let node_id = self.graph.add_node(Some(o));
ObjectIndex::new(node_id, 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(&self, index: ObjectIndex) -> Option<&O> {
self.graph.node_weight(index.into()).map(|node| {
node.as_ref()
.expect("internal error: Asg::get missing Node data")
.into()
})
}
/// 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](super::object) for more
/// information and rationale on this behavior.
///
/// The `mut_` prefix of this method is intended to emphasize that,
/// unlike traditional `map` methods,
/// this does not take and return ownership;
/// the ASG is most often interacted with via mutable reference.
///
/// 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 fn mut_map_obj(
&mut self,
index: ObjectIndex,
f: impl FnOnce(O) -> O,
) -> ObjectIndex {
let obj_container =
self.graph.node_weight_mut(index.into()).diagnostic_expect(
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."),
],
"invalid ObjectIndex: data are missing from the ASG",
);
// Any function borrowing from the graph ought to also be responsible
// for returning it in all possible code paths,
// as we are.
// And error here means that this must not be the case,
// or that we're breaking encapsulation somewhere.
let cur_obj = obj_container.take().diagnostic_expect(
vec![
index.internal_error(
"this object was borrowed from the graph and \
was not returned",
),
index.help(
"this means that some operation used take() on the object",
),
index.help(
" container but never replaced it with an updated object",
),
index.help(
" after the operation completed, which should not \
be possible.",
),
],
"inaccessible ObjectIndex: object has not been returned to the ASG",
);
let new_obj: Object = f(cur_obj.into()).into();
let new_span = new_obj.span();
// This is where we replace the object that we borrowed,
// as referenced in the above panic.
obj_container.replace(new_obj);
index.map_span(|_| new_span)
}
/// Map over an inner object that is referenced by an identifier.
///
/// This uses [`Self::mut_map_obj`];
/// see that method for more information,
/// especially as it relates to panics.
///
/// _This method is intended to be used when the system expects the
/// identifier must exist on the graph and be associated with an
/// object._
/// This panic-happy method is dangerous if used sloppily,
/// so it is currently available only for ASG tests.
/// If production code is to make use of this concept,
/// it should either first ensure the identifier exists and retrieve
/// it by [`ObjectIndex`],
/// or this method should be modified to be able to return lookup
/// errors.
///
/// Panics
/// ======
/// In addition to the reasons listed in [`Self::mut_map_obj`],
/// this will also panic if:
///
/// 1. The identifier does not exist on the graph;
/// 2. The identifier is opaque (has no edge to any object on the
/// graph).
#[cfg(test)]
pub(super) fn mut_map_obj_by_ident(
&mut self,
ident: SPair,
f: impl FnOnce(O) -> O,
) {
use crate::fmt::{DisplayWrapper, TtQuote};
use petgraph::Direction;
let oi = self
.lookup(ident.symbol())
.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::::new(ni, ident.span()))
.diagnostic_expect(
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."
),
],
&format!(
"opaque identifier: {} has no object binding",
TtQuote::wrap(ident),
),
);
// We do not care about the updated ObjectIndex from `mut_map_obj`
// since it was ephemeral for this operation.
let _ = self.mut_map_obj(oi, f);
}
/// 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) -> 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, name: SymbolId) -> Option> {
let i = name.as_usize();
self.index
.get(i)
.filter(|ni| ni.index() > 0)
.map(|ni| ObjectIndex::new(*ni, UNKNOWN_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(
&mut self,
identi: ObjectIndex,
depi: ObjectIndex,
) {
self.graph
.update_edge(identi.into(), depi.into(), Default::default());
}
/// Check whether `dep` is a dependency of `ident`.
#[inline]
pub fn has_dep(
&self,
ident: ObjectIndex,
dep: ObjectIndex,
) -> 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, ObjectIndex) {
let identi = self.lookup_or_missing(ident);
let depi = self.lookup_or_missing(dep);
self.graph
.update_edge(identi.into(), depi.into(), Default::default());
(identi, depi)
}
}
#[cfg(test)]
mod test {
use super::super::error::AsgError;
use super::*;
use crate::{num::Dim, span::dummy::*, sym::GlobalSymbolIntern};
use std::assert_matches::assert_matches;
type Sut = Asg;
#[test]
fn create_with_capacity() {
let node_capacity = 100;
let edge_capacity = 300;
let sut = Sut::with_capacity(node_capacity, edge_capacity);
let (nc, ec) = sut.graph.capacity();
assert!(nc >= node_capacity);
assert!(ec >= edge_capacity);
assert!(sut.index.capacity() >= node_capacity);
}
#[test]
fn declare_new_unique_idents() -> AsgResult<()> {
let mut sut = Sut::new();
// NB: The index ordering is important! We first use a larger
// index to create a gap, and then use an index within that gap
// to ensure that it's not considered an already-defined
// identifier.
let syma = "syma".into();
let symb = "symab".into();
let nodea = sut.declare(
SPair(syma, S1),
IdentKind::Meta,
Source {
desc: Some("a".into()),
..Default::default()
},
)?;
let nodeb = sut.declare(
SPair(symb, S2),
IdentKind::Worksheet,
Source {
desc: Some("b".into()),
..Default::default()
},
)?;
assert_ne!(nodea, nodeb);
let givena = sut.get_ident(nodea).unwrap();
assert_eq!(SPair(syma, S1), givena.name());
assert_eq!(Some(&IdentKind::Meta), givena.kind());
assert_eq!(
Some(&Source {
desc: Some("a".into()),
..Default::default()
},),
givena.src()
);
let givenb = sut.get_ident(nodeb).unwrap();
assert_eq!(SPair(symb, S2), givenb.name());
assert_eq!(Some(&IdentKind::Worksheet), givenb.kind());
assert_eq!(
Some(&Source {
desc: Some("b".into()),
..Default::default()
}),
givenb.src()
);
Ok(())
}
#[test]
fn declare_kind_auto_root() -> AsgResult<()> {
let mut sut = Sut::new();
let auto_kind = IdentKind::Worksheet;
// Sanity check, in case this changes.
assert!(auto_kind.is_auto_root());
let auto_root_node = sut.declare(
SPair("auto_root".into(), S1),
auto_kind,
Default::default(),
)?;
// Should have been automatically added as a root.
assert!(sut
.graph
.contains_edge(sut.root_node, auto_root_node.into()));
let no_auto_kind = IdentKind::Tpl;
assert!(!no_auto_kind.is_auto_root());
let no_auto_root_node = sut.declare(
SPair("no_auto_root".into(), S2),
no_auto_kind,
Default::default(),
)?;
// Non-auto-roots should _not_ be added as roots automatically.
assert!(!sut
.graph
.contains_edge(sut.root_node, no_auto_root_node.into()));
Ok(())
}
#[test]
fn lookup_by_symbol() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "lookup".into();
let node = sut.declare(
SPair(sym, S1),
IdentKind::Meta,
Source {
generated: true,
..Default::default()
},
)?;
assert_eq!(Some(node), sut.lookup(sym));
Ok(())
}
#[test]
fn declare_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symdup".into();
let src = Source {
desc: Some("orig".into()),
..Default::default()
};
// Set up an object to fail redeclaration.
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
let result =
sut.declare(SPair(sym, S2), IdentKind::Meta, Source::default());
assert_matches!(result, Err(AsgError::IdentTransition(..)));
// The node should have been restored.
assert_eq!(Some(&src), sut.get_ident(node).unwrap().src());
Ok(())
}
#[test]
fn declare_extern_returns_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symext".into();
let src = Source::default();
let kind = IdentKind::Class(Dim::Matrix);
let node =
sut.declare_extern(SPair(sym, S1), kind.clone(), src.clone())?;
let resrc = Source {
desc: Some("redeclare".into()),
..Default::default()
};
let redeclare =
sut.declare_extern(SPair(sym, S2), kind.clone(), resrc.clone())?;
assert_eq!(node, redeclare);
Ok(())
}
// Builds upon declare_returns_existing.
#[test]
fn declare_extern_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symdup".into();
let src = Source {
desc: Some("orig".into()),
..Default::default()
};
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
// Changes kind, which is invalid.
let result = sut.declare_extern(
SPair(sym, S2),
IdentKind::Worksheet,
Source::default(),
);
assert_matches!(result, Err(AsgError::IdentTransition(..)));
// The node should have been restored.
assert_eq!(Some(&src), sut.get_ident(node).unwrap().src());
Ok(())
}
#[test]
fn add_fragment_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "tofrag".into();
let src = Source {
generated: true,
..Default::default()
};
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
let fragment = "a fragment".intern();
let node_with_frag = sut.set_fragment(SPair(sym, S2), fragment)?;
// Attaching a fragment should _replace_ the node, not create a
// new one
assert_eq!(
node, node_with_frag,
"fragment node does not match original node"
);
let obj = sut.get_ident(node).unwrap();
assert_eq!(SPair(sym, S1), obj.name());
assert_eq!(Some(&IdentKind::Meta), obj.kind());
assert_eq!(Some(&src), obj.src());
assert_eq!(Some(fragment), obj.fragment());
Ok(())
}
#[test]
fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "failfrag".into();
let src = Source {
generated: true,
..Default::default()
};
// The failure will come from terr below, not this.
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
// The first set will succeed.
sut.set_fragment(SPair(sym, S2), "".into())?;
// This will fail.
let result = sut.set_fragment(SPair(sym, S3), "".into());
// The node should have been restored.
let obj = sut.get_ident(node).unwrap();
assert_eq!(SPair(sym, S1), obj.name());
assert_matches!(result, Err(AsgError::IdentTransition(..)));
Ok(())
}
#[test]
fn add_ident_dep_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "sym".into();
let dep = "dep".into();
let symnode =
sut.declare(SPair(sym, S1), IdentKind::Meta, Source::default())?;
let depnode =
sut.declare(SPair(dep, S2), IdentKind::Meta, Source::default())?;
sut.add_dep(symnode, depnode);
assert!(sut.has_dep(symnode, depnode));
// sanity check if we re-add a dep
sut.add_dep(symnode, depnode);
assert!(sut.has_dep(symnode, depnode));
Ok(())
}
// same as above test
#[test]
fn add_dep_lookup_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let _ = sut.declare(sym, IdentKind::Meta, Source::default())?;
let _ = sut.declare(dep, IdentKind::Meta, Source::default())?;
let (symnode, depnode) = sut.add_dep_lookup(sym, dep);
assert!(sut.has_dep(symnode, depnode));
Ok(())
}
#[test]
fn add_dep_lookup_missing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
// both of these are missing
let (symnode, depnode) = sut.add_dep_lookup(sym, dep);
assert!(sut.has_dep(symnode, depnode));
assert_eq!(sym, sut.get_ident(symnode).unwrap().name());
assert_eq!(dep, sut.get_ident(depnode).unwrap().name());
Ok(())
}
#[test]
fn declare_return_missing_symbol() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
// both of these are missing, see add_dep_lookup_missing
let (symnode, _) = sut.add_dep_lookup(sym, dep);
let src = Source {
desc: Some("redeclare missing".into()),
..Default::default()
};
// Check with a declared value
let declared = sut.declare(sym, IdentKind::Meta, src.clone())?;
assert_eq!(symnode, declared);
let obj = sut.get_ident(declared).unwrap();
assert_eq!(sym, obj.name());
assert_eq!(Some(&IdentKind::Meta), obj.kind());
assert_eq!(Some(&src), obj.src());
Ok(())
}
#[test]
fn mut_map_narrows_and_modifies() {
let mut sut = Sut::new();
let id_a = SPair("foo".into(), S1);
let id_b = SPair("bar".into(), S2);
let oi = sut.create(Ident::Missing(id_a));
// This is the method under test.
// It should narrow to an `Ident` because `oi` was `create`'d with
// an `Ident`.
let oi_new = sut.mut_map_obj(oi, |ident| {
assert_eq!(ident, Ident::Missing(id_a));
// Replace the identifier
Ident::Missing(id_b)
});
// These would not typically be checked by the caller;
// they are intended for debugging.
assert_eq!(S1, oi.into());
assert_eq!(S2, oi_new.into());
// A change in span does not change its equivalence.
assert_eq!(oi_new, oi);
// Ensure that the graph was updated with the new object from the
// above method.
assert_eq!(&Ident::Missing(id_b), sut.get(oi).unwrap(),);
}
}