2020-01-12 22:59:16 -05:00
|
|
|
|
// Graph abstraction
|
|
|
|
|
//
|
2023-01-17 23:09:25 -05:00
|
|
|
|
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
|
2020-03-06 11:05:18 -05:00
|
|
|
|
//
|
|
|
|
|
// This file is part of TAME.
|
2020-01-12 22:59:16 -05:00
|
|
|
|
//
|
|
|
|
|
// 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/>.
|
|
|
|
|
|
2023-01-17 21:57:50 -05:00
|
|
|
|
//! Abstract semantic graph.
|
2023-03-10 14:11:55 -05:00
|
|
|
|
//!
|
|
|
|
|
//! ![Visualization of ASG ontology](../ontviz.svg)
|
2020-01-12 22:59:16 -05:00
|
|
|
|
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
use self::object::{
|
2023-04-24 09:44:02 -04:00
|
|
|
|
DynObjectRel, NameableMissingObject, ObjectIndexRelTo, ObjectRelFrom,
|
|
|
|
|
ObjectRelTy, ObjectRelatable, Root,
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
};
|
2023-01-23 11:40:10 -05:00
|
|
|
|
|
2023-05-16 14:52:01 -04:00
|
|
|
|
use super::{air::EnvScopeKind, AsgError, Object, ObjectIndex, ObjectKind};
|
2023-01-17 21:57:50 -05:00
|
|
|
|
use crate::{
|
|
|
|
|
diagnose::{panic::DiagnosticPanic, Annotate, AnnotatedSpan},
|
|
|
|
|
f::Functor,
|
|
|
|
|
global,
|
|
|
|
|
parse::{util::SPair, Token},
|
2023-01-31 22:00:51 -05:00
|
|
|
|
span::Span,
|
2023-01-17 21:57:50 -05:00
|
|
|
|
sym::SymbolId,
|
|
|
|
|
};
|
2023-01-27 09:54:26 -05:00
|
|
|
|
use fxhash::FxHashMap;
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
use petgraph::{
|
|
|
|
|
graph::{DiGraph, Graph, NodeIndex},
|
|
|
|
|
visit::EdgeRef,
|
|
|
|
|
Direction,
|
|
|
|
|
};
|
2023-01-17 21:57:50 -05:00
|
|
|
|
use std::{fmt::Debug, result::Result};
|
2020-01-12 22:59:16 -05:00
|
|
|
|
|
2023-01-17 22:58:41 -05:00
|
|
|
|
pub mod object;
|
2023-02-07 14:59:36 -05:00
|
|
|
|
pub mod visit;
|
2023-02-22 23:16:53 -05:00
|
|
|
|
pub mod xmli;
|
2023-01-17 22:58:41 -05:00
|
|
|
|
|
2023-04-18 14:05:01 -04:00
|
|
|
|
use object::ObjectContainer;
|
2023-01-17 22:58:41 -05:00
|
|
|
|
|
2020-04-09 11:34:30 -04:00
|
|
|
|
/// Datatype representing node and edge indexes.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
pub trait IndexType = petgraph::graph::IndexType;
|
2020-04-09 11:34:30 -04:00
|
|
|
|
|
2022-05-11 16:38:59 -04:00
|
|
|
|
/// 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>;
|
|
|
|
|
|
2023-01-31 16:37:25 -05:00
|
|
|
|
/// 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`].
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
///
|
|
|
|
|
/// 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>);
|
2022-05-11 16:38:59 -04:00
|
|
|
|
|
2023-01-10 15:06:24 -05:00
|
|
|
|
/// Each node of the graph.
|
2023-01-23 13:35:14 -05:00
|
|
|
|
type Node = ObjectContainer;
|
2022-05-11 16:38:59 -04:00
|
|
|
|
|
|
|
|
|
/// Index size for Graph nodes and edges.
|
|
|
|
|
type Ix = global::ProgSymSize;
|
|
|
|
|
|
2023-01-17 22:58:41 -05:00
|
|
|
|
/// An abstract semantic graph (ASG) of [objects](object).
|
2022-05-11 16:38:59 -04:00
|
|
|
|
///
|
|
|
|
|
/// This implementation is currently based on [`petgraph`].
|
|
|
|
|
///
|
2020-01-12 22:59:16 -05:00
|
|
|
|
/// Objects are never deleted from the graph,
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// so [`ObjectIndex`]s will remain valid for the lifetime of the ASG.
|
2020-01-12 22:59:16 -05:00
|
|
|
|
///
|
|
|
|
|
/// For more information,
|
|
|
|
|
/// see the [module-level documentation][self].
|
2022-05-12 15:44:32 -04:00
|
|
|
|
pub struct Asg {
|
2022-05-11 16:38:59 -04:00
|
|
|
|
/// Directed graph on which objects are stored.
|
2023-04-28 15:28:21 -04:00
|
|
|
|
graph: DiGraph<Node, AsgEdge, Ix>,
|
2022-05-11 16:38:59 -04:00
|
|
|
|
|
tamer: asg::air: Begin to introduce explicit scope testing
There's a lot of documentation on this in the commit itself, but this stems
from
a) frustration with trying to understand how the system needs to operate
with all of the objects involved; and
b) recognizing that if I'm having difficulty, then others reading the
system later on (including myself) and possibly looking to improve upon
it are going to have a whole lot of trouble.
Identifier scope is something I've been mulling over for years, and more
formally for the past couple of months. This finally begins to formalize
that, out of frustration with package imports. But it will be a weight
lifted off of me as well, with issues of scope always looming.
This demonstrates a declarative means of testing for scope by scanning the
entire graph in tests to determine where an identifier has been
scoped. Since no such scoping has been implemented yet, the tests
demonstrate how they will look, but otherwise just test for current
behavior. There is more existing behavior to check, and further there will
be _references_ to check, as they'll also leave a trail of scope indexing
behind as part of the resolution process.
See the documentation introduced by this commit for more information on
that part of this commit.
Introducing the graph scanning, with the ASG's static assurances, required
more lowering of dynamic types into the static types required by the
API. This was itself a confusing challenge that, while not all that bad in
retrospect, was something that I initially had some trouble with. The
documentation includes clarifying remarks that hopefully make it all
understandable.
DEV-13162
2023-05-12 12:41:51 -04:00
|
|
|
|
/// Environment cache of [`SymbolId`][crate::sym::SymbolId] to
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// [`ObjectIndex`]es.
|
2023-04-03 14:33:07 -04:00
|
|
|
|
///
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// 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`].
|
2023-04-03 14:33:07 -04:00
|
|
|
|
///
|
|
|
|
|
/// 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
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// to a given node.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
/// Note that,
|
|
|
|
|
/// while we store [`NodeIndex`] internally,
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// the public API encapsulates it within an [`ObjectIndex`].
|
2023-04-20 14:55:20 -04:00
|
|
|
|
index: FxHashMap<
|
|
|
|
|
(ObjectRelTy, SymbolId, ObjectIndex<Object>),
|
2023-05-16 14:52:01 -04:00
|
|
|
|
EnvScopeKind<ObjectIndex<Object>>,
|
2023-04-20 14:55:20 -04:00
|
|
|
|
>,
|
tamer: asg: Track roots on graph
Previously, since the graph contained only identifiers, discovered roots
were stored in a separate vector and exposed to the caller. This not only
leaked details, but added complexity; this was left over from the
refactoring of the proof-of-concept linker some time ago.
This moves the root management into the ASG itself, mostly, with one item
being left over for now in the asg_builder (eligibility classifications).
There are two roots that were added automatically:
- __yield
- __worksheet
The former has been removed and is now expected to be explicitly mapped in
the return map, which is now enforced with an extern in `core/base`. This
is still special, in the sense that it is explicitly referenced by the
generated code, but there's nothing inherently special about it and I'll
continue to generalize it into oblivion in the future, such that the final
yield is just a convention.
`__worksheet` is the only symbol of type `IdentKind::Worksheet`, and so that
was generalized just as the meta and map entries were.
The goal in the future will be to have this more under the control of the
source language, and to consolodate individual roots under packages, so that
the _actual_ roots are few.
As far as the actual ASG goes: this introduces a single root node that is
used as the sole reference for reachability analysis and topological
sorting. The edges of that root node replace the vector that was removed.
DEV-11864
2022-05-17 10:42:05 -04:00
|
|
|
|
|
|
|
|
|
/// The root node used for reachability analysis and topological
|
|
|
|
|
/// sorting.
|
|
|
|
|
root_node: NodeIndex<Ix>,
|
2022-05-11 16:38:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-27 10:22:54 -05:00
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
|
impl Default for Asg {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-12 15:44:32 -04:00
|
|
|
|
impl Asg {
|
2022-05-11 16:38:59 -04:00
|
|
|
|
/// Create a new ASG.
|
|
|
|
|
///
|
|
|
|
|
/// See also [`with_capacity`](Asg::with_capacity).
|
|
|
|
|
pub fn new() -> Self {
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
|
// TODO: Determine a proper initial capacity.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
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);
|
2023-01-27 09:54:26 -05:00
|
|
|
|
let index =
|
|
|
|
|
FxHashMap::with_capacity_and_hasher(objects, Default::default());
|
2022-05-11 16:38:59 -04:00
|
|
|
|
|
tamer: asg: Track roots on graph
Previously, since the graph contained only identifiers, discovered roots
were stored in a separate vector and exposed to the caller. This not only
leaked details, but added complexity; this was left over from the
refactoring of the proof-of-concept linker some time ago.
This moves the root management into the ASG itself, mostly, with one item
being left over for now in the asg_builder (eligibility classifications).
There are two roots that were added automatically:
- __yield
- __worksheet
The former has been removed and is now expected to be explicitly mapped in
the return map, which is now enforced with an extern in `core/base`. This
is still special, in the sense that it is explicitly referenced by the
generated code, but there's nothing inherently special about it and I'll
continue to generalize it into oblivion in the future, such that the final
yield is just a convention.
`__worksheet` is the only symbol of type `IdentKind::Worksheet`, and so that
was generalized just as the meta and map entries were.
The goal in the future will be to have this more under the control of the
source language, and to consolodate individual roots under packages, so that
the _actual_ roots are few.
As far as the actual ASG goes: this introduces a single root node that is
used as the sole reference for reachability analysis and topological
sorting. The edges of that root node replace the vector that was removed.
DEV-11864
2022-05-17 10:42:05 -04:00
|
|
|
|
// 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.
|
2023-01-31 22:00:51 -05:00
|
|
|
|
let root_node = graph.add_node(Object::Root(Root).into());
|
tamer: asg: Track roots on graph
Previously, since the graph contained only identifiers, discovered roots
were stored in a separate vector and exposed to the caller. This not only
leaked details, but added complexity; this was left over from the
refactoring of the proof-of-concept linker some time ago.
This moves the root management into the ASG itself, mostly, with one item
being left over for now in the asg_builder (eligibility classifications).
There are two roots that were added automatically:
- __yield
- __worksheet
The former has been removed and is now expected to be explicitly mapped in
the return map, which is now enforced with an extern in `core/base`. This
is still special, in the sense that it is explicitly referenced by the
generated code, but there's nothing inherently special about it and I'll
continue to generalize it into oblivion in the future, such that the final
yield is just a convention.
`__worksheet` is the only symbol of type `IdentKind::Worksheet`, and so that
was generalized just as the meta and map entries were.
The goal in the future will be to have this more under the control of the
source language, and to consolodate individual roots under packages, so that
the _actual_ roots are few.
As far as the actual ASG goes: this introduces a single root node that is
used as the sole reference for reachability analysis and topological
sorting. The edges of that root node replace the vector that was removed.
DEV-11864
2022-05-17 10:42:05 -04:00
|
|
|
|
|
2022-05-11 16:38:59 -04:00
|
|
|
|
Self {
|
|
|
|
|
graph,
|
|
|
|
|
index,
|
tamer: asg: Track roots on graph
Previously, since the graph contained only identifiers, discovered roots
were stored in a separate vector and exposed to the caller. This not only
leaked details, but added complexity; this was left over from the
refactoring of the proof-of-concept linker some time ago.
This moves the root management into the ASG itself, mostly, with one item
being left over for now in the asg_builder (eligibility classifications).
There are two roots that were added automatically:
- __yield
- __worksheet
The former has been removed and is now expected to be explicitly mapped in
the return map, which is now enforced with an extern in `core/base`. This
is still special, in the sense that it is explicitly referenced by the
generated code, but there's nothing inherently special about it and I'll
continue to generalize it into oblivion in the future, such that the final
yield is just a convention.
`__worksheet` is the only symbol of type `IdentKind::Worksheet`, and so that
was generalized just as the meta and map entries were.
The goal in the future will be to have this more under the control of the
source language, and to consolodate individual roots under packages, so that
the _actual_ roots are few.
As far as the actual ASG goes: this introduces a single root node that is
used as the sole reference for reachability analysis and topological
sorting. The edges of that root node replace the vector that was removed.
DEV-11864
2022-05-17 10:42:05 -04:00
|
|
|
|
root_node,
|
2022-05-11 16:38:59 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the underlying Graph
|
2022-05-12 15:44:32 -04:00
|
|
|
|
pub fn into_inner(self) -> DiGraph<Node, AsgEdge, Ix> {
|
2022-05-11 16:38:59 -04:00
|
|
|
|
self.graph
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 09:49:50 -04:00
|
|
|
|
/// Number of [`Object`]s on the graph.
|
|
|
|
|
///
|
|
|
|
|
/// This is equivalent to the number of nodes on the graph at the time
|
|
|
|
|
/// of writing,
|
|
|
|
|
/// but that may not always be the case.
|
|
|
|
|
fn object_count(&self) -> usize {
|
|
|
|
|
self.graph.node_count()
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-02 16:07:25 -04:00
|
|
|
|
pub(super) fn try_index<
|
|
|
|
|
O: ObjectRelatable,
|
|
|
|
|
OS: ObjectIndexRelTo<O>,
|
|
|
|
|
S: Into<SymbolId>,
|
|
|
|
|
>(
|
|
|
|
|
&mut self,
|
|
|
|
|
imm_env: OS,
|
|
|
|
|
name: S,
|
2023-05-16 14:52:01 -04:00
|
|
|
|
eoi: EnvScopeKind<ObjectIndex<O>>,
|
2023-05-02 16:07:25 -04:00
|
|
|
|
) -> Result<(), ObjectIndex<O>> {
|
|
|
|
|
let sym = name.into();
|
2023-05-16 14:52:01 -04:00
|
|
|
|
let prev = self.index.insert(
|
|
|
|
|
(O::rel_ty(), sym, imm_env.widen()),
|
|
|
|
|
eoi.map(ObjectIndex::widen),
|
|
|
|
|
);
|
2023-05-02 16:07:25 -04:00
|
|
|
|
|
|
|
|
|
match prev {
|
|
|
|
|
None => Ok(()),
|
2023-05-16 14:52:01 -04:00
|
|
|
|
Some(eoi) => Err(eoi.into_inner().must_narrow_into::<O>()),
|
2023-05-02 16:07:25 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// Index the provided symbol `name` as representing the
|
|
|
|
|
/// [`ObjectIndex`] in the immediate environment `imm_env`.
|
|
|
|
|
///
|
|
|
|
|
/// 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.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
///
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// This index permits `O(1)` object lookups.
|
2023-04-03 14:33:07 -04:00
|
|
|
|
/// The term "immediate environment" is meant to convey that this index
|
|
|
|
|
/// applies only to the provided `imm_env` node and does not
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// propagate to any other objects that share this environment.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
///
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// After an object is indexed it is not expected to be re-indexed
|
2022-05-11 16:38:59 -04:00
|
|
|
|
/// to another node.
|
|
|
|
|
/// Debug builds contain an assertion that will panic in this instance.
|
2023-04-20 14:55:20 -04:00
|
|
|
|
pub(super) fn index<
|
|
|
|
|
O: ObjectRelatable,
|
2023-04-24 09:44:02 -04:00
|
|
|
|
OS: ObjectIndexRelTo<O>,
|
2023-04-04 14:38:37 -04:00
|
|
|
|
S: Into<SymbolId>,
|
|
|
|
|
>(
|
2023-04-03 14:33:07 -04:00
|
|
|
|
&mut self,
|
2023-04-04 14:38:37 -04:00
|
|
|
|
imm_env: OS,
|
|
|
|
|
name: S,
|
2023-05-16 14:52:01 -04:00
|
|
|
|
eoi: EnvScopeKind<ObjectIndex<O>>,
|
2023-04-03 14:33:07 -04:00
|
|
|
|
) {
|
2023-04-04 14:38:37 -04:00
|
|
|
|
let sym = name.into();
|
2023-05-16 14:52:01 -04:00
|
|
|
|
let prev = self.try_index(imm_env, sym, eoi);
|
2022-05-11 16:38:59 -04:00
|
|
|
|
|
|
|
|
|
// We should never overwrite indexes
|
2023-04-04 14:38:37 -04:00
|
|
|
|
#[allow(unused_variables)] // used only for debug
|
2023-04-17 15:17:49 -04:00
|
|
|
|
#[allow(unused_imports)]
|
2023-05-02 16:07:25 -04:00
|
|
|
|
if let Err(prev_oi) = prev {
|
2023-04-17 15:17:49 -04:00
|
|
|
|
use crate::fmt::{DisplayWrapper, TtQuote};
|
2023-04-04 14:38:37 -04:00
|
|
|
|
crate::debug_diagnostic_panic!(
|
|
|
|
|
vec![
|
2023-04-24 09:44:02 -04:00
|
|
|
|
imm_env.widen().note("at this scope boundary"),
|
2023-04-04 14:38:37 -04:00
|
|
|
|
prev_oi.note("previously indexed identifier was here"),
|
2023-05-16 14:52:01 -04:00
|
|
|
|
eoi.internal_error(
|
2023-04-04 14:38:37 -04:00
|
|
|
|
"this identifier has already been indexed at the above scope boundary"
|
|
|
|
|
),
|
2023-05-16 14:52:01 -04:00
|
|
|
|
eoi.help(
|
2023-04-04 14:38:37 -04:00
|
|
|
|
"this is a bug in the system responsible for analyzing \
|
|
|
|
|
identifier scope;"
|
|
|
|
|
),
|
2023-05-16 14:52:01 -04:00
|
|
|
|
eoi.help(
|
2023-04-04 14:38:37 -04:00
|
|
|
|
" you can try to work around it by duplicating the definition of "
|
|
|
|
|
),
|
2023-05-16 14:52:01 -04:00
|
|
|
|
eoi.help(
|
2023-04-04 14:38:37 -04:00
|
|
|
|
format!(
|
|
|
|
|
" {} as a _new_ identifier with a different name.",
|
|
|
|
|
TtQuote::wrap(sym),
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
"re-indexing of identifier at scope boundary",
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-05-11 16:38:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// Lookup `name or add a missing object to the graph relative to
|
2023-04-03 14:33:07 -04:00
|
|
|
|
/// the immediate environment `imm_env` and return a reference to it.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
///
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// The provided span is necessary to seed the missing object with
|
|
|
|
|
/// some sort of context to aid in debugging why a missing object
|
2022-12-15 12:07:58 -05:00
|
|
|
|
/// was introduced to the graph.
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// The provided span will be used by the returned [`ObjectIndex`] even
|
|
|
|
|
/// if an object exists on the graph,
|
2023-01-17 16:31:13 -05:00
|
|
|
|
/// which can be used for retaining information on the location that
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// requested the object.
|
|
|
|
|
/// To retrieve the span of a previously declared object,
|
|
|
|
|
/// you must resolve the [`ObjectIndex`] and inspect it.
|
2022-12-15 12:07:58 -05:00
|
|
|
|
///
|
2023-04-20 14:55:20 -04:00
|
|
|
|
/// See [`Self::index`] for more information.
|
2023-04-21 16:24:11 -04:00
|
|
|
|
pub(super) fn lookup_or_missing<O: ObjectRelatable>(
|
2022-12-22 14:24:40 -05:00
|
|
|
|
&mut self,
|
2023-04-24 09:44:02 -04:00
|
|
|
|
imm_env: impl ObjectIndexRelTo<O>,
|
2023-04-03 14:33:07 -04:00
|
|
|
|
name: SPair,
|
2023-04-20 14:55:20 -04:00
|
|
|
|
) -> ObjectIndex<O>
|
|
|
|
|
where
|
|
|
|
|
O: NameableMissingObject,
|
|
|
|
|
{
|
2023-04-03 14:33:07 -04:00
|
|
|
|
self.lookup(imm_env, name).unwrap_or_else(|| {
|
2023-04-20 14:55:20 -04:00
|
|
|
|
let oi = self.create(O::missing(name));
|
2023-05-16 14:52:01 -04:00
|
|
|
|
|
|
|
|
|
// TODO: This responsibility is split between `Asg` and
|
|
|
|
|
// `AirAggregateCtx`!
|
|
|
|
|
let eoi = EnvScopeKind::Visible(oi);
|
|
|
|
|
|
|
|
|
|
self.index(imm_env, name.symbol(), eoi);
|
2023-04-04 14:38:37 -04:00
|
|
|
|
oi
|
2022-05-11 16:38:59 -04:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-31 22:00:51 -05:00
|
|
|
|
/// 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.
|
2023-04-18 14:05:01 -04:00
|
|
|
|
pub fn root<S: Into<Span>>(&self, witness: S) -> ObjectIndex<Root> {
|
|
|
|
|
ObjectIndex::new(self.root_node, witness.into())
|
2022-05-11 16:38:59 -04:00
|
|
|
|
}
|
2020-01-12 22:59:16 -05:00
|
|
|
|
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
/// Create a new object on the graph.
|
|
|
|
|
///
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// The provided [`ObjectIndex`] will be augmented with the span
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
/// of `obj`.
|
2022-12-22 14:24:40 -05:00
|
|
|
|
pub(super) fn create<O: ObjectKind>(&mut self, obj: O) -> ObjectIndex<O> {
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
let o = obj.into();
|
|
|
|
|
let span = o.span();
|
2023-01-10 15:06:24 -05:00
|
|
|
|
let node_id = self.graph.add_node(ObjectContainer::from(o.into()));
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
|
2022-12-22 14:24:40 -05:00
|
|
|
|
ObjectIndex::new(node_id, span)
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
}
|
|
|
|
|
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
/// Add an edge from the [`Object`] represented by the
|
|
|
|
|
/// [`ObjectIndex`] `from_oi` to the object represented by `to_oi`.
|
|
|
|
|
///
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
/// 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.
|
|
|
|
|
///
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
/// For more information on how the ASG's ontology is enforced statically,
|
2023-04-18 14:05:01 -04:00
|
|
|
|
/// see [`ObjectRelTo`](object::ObjectRelTo).
|
2023-03-28 11:34:05 -04:00
|
|
|
|
fn add_edge<OB: ObjectKind + ObjectRelatable>(
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
&mut self,
|
2023-03-28 11:34:05 -04:00
|
|
|
|
from_oi: impl ObjectIndexRelTo<OB>,
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
to_oi: ObjectIndex<OB>,
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
ctx_span: Option<Span>,
|
2023-03-28 11:34:05 -04:00
|
|
|
|
) {
|
2023-01-31 16:37:25 -05:00
|
|
|
|
self.graph.add_edge(
|
2023-03-28 11:34:05 -04:00
|
|
|
|
from_oi.widen().into(),
|
2023-01-31 16:37:25 -05:00
|
|
|
|
to_oi.into(),
|
2023-03-28 11:34:05 -04:00
|
|
|
|
(from_oi.src_rel_ty(), OB::rel_ty(), ctx_span),
|
2023-01-31 16:37:25 -05:00
|
|
|
|
);
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// Retrieve an object from the graph by [`ObjectIndex`].
|
2020-01-12 22:59:16 -05:00
|
|
|
|
///
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// Since an [`ObjectIndex`] should only be produced by an [`Asg`],
|
2020-01-12 22:59:16 -05:00
|
|
|
|
/// 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.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
#[inline]
|
2022-12-22 16:32:21 -05:00
|
|
|
|
pub fn get<O: ObjectKind>(&self, index: ObjectIndex<O>) -> Option<&O> {
|
2023-01-10 15:06:24 -05:00
|
|
|
|
self.graph
|
|
|
|
|
.node_weight(index.into())
|
|
|
|
|
.map(ObjectContainer::get)
|
2022-05-11 16:38:59 -04:00
|
|
|
|
}
|
2020-01-12 22:59:16 -05:00
|
|
|
|
|
2023-01-17 16:31:13 -05:00
|
|
|
|
/// Attempt to map over an inner [`Object`] referenced by
|
|
|
|
|
/// [`ObjectIndex`].
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
///
|
|
|
|
|
/// The type `O` is the expected type of the [`Object`],
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// which should be known to the caller based on the provied
|
|
|
|
|
/// [`ObjectIndex`].
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
/// This method will attempt to narrow to that object type,
|
|
|
|
|
/// panicing if there is a mismatch;
|
2023-01-17 22:58:41 -05:00
|
|
|
|
/// see the [`object` module documentation](object) for more
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
/// 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:
|
|
|
|
|
///
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// 1. If the provided [`ObjectIndex`] references a node index that is
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
/// not present on the graph;
|
2022-12-22 14:24:40 -05:00
|
|
|
|
/// 2. If the node referenced by [`ObjectIndex`] exists but its container
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
/// 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.
|
2022-12-22 14:24:40 -05:00
|
|
|
|
#[must_use = "returned ObjectIndex has a possibly-updated and more relevant span"]
|
2023-01-17 16:31:13 -05:00
|
|
|
|
pub(super) fn try_map_obj<O: ObjectKind, E>(
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
&mut self,
|
2022-12-22 14:24:40 -05:00
|
|
|
|
index: ObjectIndex<O>,
|
2023-01-17 16:31:13 -05:00
|
|
|
|
f: impl FnOnce(O) -> Result<O, (O, E)>,
|
|
|
|
|
) -> Result<ObjectIndex<O>, E> {
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
let obj_container =
|
|
|
|
|
self.graph.node_weight_mut(index.into()).diagnostic_expect(
|
2023-01-12 16:17:41 -05:00
|
|
|
|
|| diagnostic_node_missing_desc(index),
|
2022-12-22 14:24:40 -05:00
|
|
|
|
"invalid ObjectIndex: data are missing from the ASG",
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
);
|
|
|
|
|
|
2023-01-17 16:31:13 -05:00
|
|
|
|
obj_container
|
|
|
|
|
.try_replace_with(f)
|
|
|
|
|
.map(|()| index.overwrite(obj_container.get::<Object>().span()))
|
tamer: Initial concept for AIR/ASG Expr
This begins to place expressions on the graph---something that I've been
thinking about for a couple of years now, so it's interesting to finally be
doing it.
This is going to evolve; I want to get some things committed so that it's
clear how I'm moving forward. The ASG makes things a bit awkward for a
number of reasons:
1. I'm dealing with older code where I had a different model of doing
things;
2. It's mutable, rather than the mostly-functional lowering pipeline;
3. We're dealing with an aggregate ever-evolving blob of data (the graph)
rather than a stream of tokens; and
4. We don't have as many type guarantees.
I've shown with the lowering pipeline that I'm able to take a mutable
reference and convert it into something that's both functional and
performant, where I remove it from its container (an `Option`), create a new
version of it, and place it back. Rust is able to optimize away the memcpys
and such and just directly manipulate the underlying value, which is often a
register with all of the inlining.
_But_ this is a different scenario now. The lowering pipeline has a narrow
context. The graph has to keep hitting memory. So we'll see how this
goes. But it's most important to get this working and measure how it
performs; I'm not trying to prematurely optimize. My attempts right now are
for the way that I wish to develop.
Speaking to #4 above, it also sucks that I'm not able to type the
relationships between nodes on the graph. Rather, it's not that I _can't_,
but a project to created a typed graph library is beyond the scope of this
work and would take far too much time. I'll leave that to a personal,
non-work project. Instead, I'm going to have to narrow the type any time
the graph is accessed. And while that sucks, I'm going to do my best to
encapsulate those details to make it as seamless as possible API-wise. The
performance hit of performing the narrowing I'm hoping will be very small
relative to all the business logic going on (a single cache miss is bound to
be far more expensive than many narrowings which are just integer
comparisons and branching)...but we'll see. Introducing branching sucks,
but branch prediction is pretty damn good in modern CPUs.
DEV-13160
2022-12-21 16:47:04 -05:00
|
|
|
|
}
|
|
|
|
|
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
/// Create an iterator over the [`ObjectIndex`]es of the outgoing edges
|
2023-02-07 14:59:36 -05:00
|
|
|
|
/// of `oi`.
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
///
|
|
|
|
|
/// 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
|
2023-04-18 14:05:01 -04:00
|
|
|
|
/// ontology defined by [`ObjectRelTo`](object::ObjectRelTo),
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
/// 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.
|
2023-01-23 11:40:10 -05:00
|
|
|
|
fn edges<'a, O: ObjectKind + ObjectRelatable + 'a>(
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
&'a self,
|
|
|
|
|
oi: ObjectIndex<O>,
|
2023-01-23 11:40:10 -05:00
|
|
|
|
) -> impl Iterator<Item = O::Rel> + 'a {
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
self.edges_dyn(oi.widen()).map(move |dyn_rel| {
|
|
|
|
|
let target_ty = dyn_rel.target_ty();
|
|
|
|
|
|
tamer: asg::graph::object::rel::DynObjectRel: Store source data
This is generic over the source, just as the target, defaulting just the
same to `ObjectIndex`.
This allows us to use only the edge information provided rather than having
to perform another lookup on the graph and then assert that we found the
correct edge. In this case, we're dealing with an `Ident->Expr` edge, of
which there is only one, but in other cases, there may be many such edges,
and it wouldn't be possible to know _which_ was referred to without also
keeping context of the previous edge in the walk.
So, in addition to avoiding more indirection and being more immune to logic
bugs, this also allows us to avoid states in `AsgTreeToXirf` for the purpose
of tracking previous edges in the current path. And it means that the tree
walk can seed further traversals in conjunction with it, if that is so
needed for deriving sources.
More cleanup will be needed, but this does well to set us up for moving
forward; I was too uncomfortable with having to do the separate
lookup. This is also a more intuitive API.
But it does have the awkward effect that now I don't need the pair---I just
need the `Object`---but I'm not going to remove it because I suspect I may
need it in the future. We'll see.
The TODO references the fact that I'm using a convenient `resolve_oi_pairs`
instead of resolving only the target first and then the source only in the
code path that needs it. I'll want to verify that Rust will properly
optimize to avoid the source resolution in branches that do not need it.
DEV-13708
2023-02-23 22:45:09 -05:00
|
|
|
|
dyn_rel.narrow_target::<O>().diagnostic_unwrap(|| {
|
2023-01-30 11:27:40 -05:00
|
|
|
|
vec![
|
|
|
|
|
oi.internal_error(format!(
|
|
|
|
|
"encountered invalid outgoing edge type {:?}",
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
target_ty,
|
2023-01-30 11:27:40 -05:00
|
|
|
|
)),
|
|
|
|
|
oi.help(
|
|
|
|
|
"this means that Asg did not enforce edge invariants \
|
|
|
|
|
during construction, which is a significant bug",
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
})
|
2023-01-23 11:40:10 -05:00
|
|
|
|
})
|
tamer: asg: Add expression edges
This introduces a number of abstractions, whose concepts are not fully
documented yet since I want to see how it evolves in practice first.
This introduces the concept of edge ontology (similar to a schema) using the
type system. Even though we are not able to determine what the graph will
look like statically---since that's determined by data fed to us at
runtime---we _can_ ensure that the code _producing_ the graph from those
data will produce a graph that adheres to its ontology.
Because of the typed `ObjectIndex`, we're also able to implement operations
that are specific to the type of object that we're operating on. Though,
since the type is not (yet?) stored on the edge itself, it is possible to
walk the graph without looking at node weights (the `ObjectContainer`) and
therefore avoid panics for invalid type assumptions, which is bad, but I
don't think that'll happen in practice, since we'll want to be resolving
nodes at some point. But I'll addres that more in the future.
Another thing to note is that walking edges is only done in tests right now,
and so there's no filtering or anything; once there are nodes (if there are
nodes) that allow for different outgoing edge types, we'll almost certainly
want filtering as well, rather than panicing. We'll also want to be able to
query for any object type, but filter only to what's permitted by the
ontology.
DEV-13160
2023-01-11 15:49:37 -05:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-07 14:59:36 -05:00
|
|
|
|
/// 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>,
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
) -> impl Iterator<Item = DynObjectRel> + 'a {
|
2023-02-07 14:59:36 -05:00
|
|
|
|
self.graph.edges(oi.into()).map(move |edge| {
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
let (src_ty, target_ty, ctx_span) = edge.weight();
|
|
|
|
|
|
|
|
|
|
DynObjectRel::new(
|
|
|
|
|
*src_ty,
|
|
|
|
|
*target_ty,
|
tamer: asg::graph::object::rel::DynObjectRel: Store source data
This is generic over the source, just as the target, defaulting just the
same to `ObjectIndex`.
This allows us to use only the edge information provided rather than having
to perform another lookup on the graph and then assert that we found the
correct edge. In this case, we're dealing with an `Ident->Expr` edge, of
which there is only one, but in other cases, there may be many such edges,
and it wouldn't be possible to know _which_ was referred to without also
keeping context of the previous edge in the walk.
So, in addition to avoiding more indirection and being more immune to logic
bugs, this also allows us to avoid states in `AsgTreeToXirf` for the purpose
of tracking previous edges in the current path. And it means that the tree
walk can seed further traversals in conjunction with it, if that is so
needed for deriving sources.
More cleanup will be needed, but this does well to set us up for moving
forward; I was too uncomfortable with having to do the separate
lookup. This is also a more intuitive API.
But it does have the awkward effect that now I don't need the pair---I just
need the `Object`---but I'm not going to remove it because I suspect I may
need it in the future. We'll see.
The TODO references the fact that I'm using a convenient `resolve_oi_pairs`
instead of resolving only the target first and then the source only in the
code path that needs it. I'll want to verify that Rust will properly
optimize to avoid the source resolution in branches that do not need it.
DEV-13708
2023-02-23 22:45:09 -05:00
|
|
|
|
oi,
|
2023-02-07 14:59:36 -05:00
|
|
|
|
ObjectIndex::<Object>::new(edge.target(), oi),
|
tamer: asg::graph: Formalize dynamic relationships (edges)
The `TreePreOrderDfs` iterator needed to expose additional edge context to
the caller (specifically, the `Span`). This was getting a bit messy, so
this consolodates everything into a new `DynObjectRel`, which also
emphasizes that it is in need of narrowing.
Packing everything up like that also allows us to return more information to
the caller without complicating the API, since the caller does not need to
be concerned with all of those values individually.
Depth is kept separate, since that is a property of the traversal and is not
stored on the graph. (Rather, it _is_ a property of the graph, but it's not
calculated until traversal. But, depth will also vary for a given node
because of cross edges, and so we cannot store any concrete depth on the
graph for a given node. Not even a canonical one, because once we start
doing inlining and common subexpression elimination, there will be shared
edges that are _not_ cross edges (the node is conceptually part of _both_
trees). Okay, enough of this rambling parenthetical.)
DEV-13708
2023-02-09 13:11:27 -05:00
|
|
|
|
*ctx_span,
|
2023-02-07 14:59:36 -05:00
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-31 16:37:25 -05:00
|
|
|
|
/// 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))
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-18 14:05:01 -04:00
|
|
|
|
/// Check whether an edge exists from `from` to `to.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn has_edge<OB: ObjectRelatable>(
|
|
|
|
|
&self,
|
|
|
|
|
from: impl ObjectIndexRelTo<OB>,
|
|
|
|
|
to: ObjectIndex<OB>,
|
|
|
|
|
) -> bool {
|
|
|
|
|
self.graph.contains_edge(from.widen().into(), to.into())
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-10 15:06:24 -05:00
|
|
|
|
pub(super) fn expect_obj<O: ObjectKind>(&self, oi: ObjectIndex<O>) -> &O {
|
|
|
|
|
let obj_container =
|
|
|
|
|
self.graph.node_weight(oi.into()).diagnostic_expect(
|
2023-01-12 16:17:41 -05:00
|
|
|
|
|| diagnostic_node_missing_desc(oi),
|
2023-01-10 15:06:24 -05:00
|
|
|
|
"invalid ObjectIndex: data are missing from the ASG",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
obj_container.get()
|
2023-01-09 12:02:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-03 14:33:07 -04:00
|
|
|
|
/// Attempt to retrieve an identifier from the graph by name relative to
|
|
|
|
|
/// the immediate environment `imm_env`.
|
2020-01-12 22:59:16 -05:00
|
|
|
|
///
|
|
|
|
|
/// Since only identifiers carry a name,
|
|
|
|
|
/// this method cannot be used to retrieve all possible objects on the
|
|
|
|
|
/// graph---for
|
|
|
|
|
/// that, see [`Asg::get`].
|
2022-05-11 16:38:59 -04:00
|
|
|
|
#[inline]
|
2023-04-26 09:49:50 -04:00
|
|
|
|
pub fn lookup<O: ObjectRelatable>(
|
2023-04-03 14:33:07 -04:00
|
|
|
|
&self,
|
2023-04-26 09:49:50 -04:00
|
|
|
|
imm_env: impl ObjectIndexRelTo<O>,
|
2023-04-03 14:33:07 -04:00
|
|
|
|
id: SPair,
|
2023-04-20 14:55:20 -04:00
|
|
|
|
) -> Option<ObjectIndex<O>> {
|
2023-05-16 14:52:01 -04:00
|
|
|
|
self.lookup_raw(imm_env, id)
|
|
|
|
|
.and_then(EnvScopeKind::in_scope)
|
|
|
|
|
.map(EnvScopeKind::into_inner)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to retrieve an identifier and its scope information from the
|
|
|
|
|
/// graph by name relative to the immediate environment `imm_env`.
|
|
|
|
|
///
|
|
|
|
|
/// See [`Self::lookup`] for more information.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub(super) fn lookup_raw<O: ObjectRelatable>(
|
|
|
|
|
&self,
|
|
|
|
|
imm_env: impl ObjectIndexRelTo<O>,
|
|
|
|
|
id: SPair,
|
|
|
|
|
) -> Option<EnvScopeKind<ObjectIndex<O>>> {
|
2023-04-20 14:55:20 -04:00
|
|
|
|
// 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.
|
2022-05-11 16:38:59 -04:00
|
|
|
|
self.index
|
2023-04-20 14:55:20 -04:00
|
|
|
|
.get(&(O::rel_ty(), id.symbol(), imm_env.widen()))
|
2023-05-16 14:52:01 -04:00
|
|
|
|
.map(|&eoi| {
|
|
|
|
|
eoi.map(|oi| oi.overwrite(id.span()).must_narrow_into::<O>())
|
|
|
|
|
})
|
2022-05-11 16:38:59 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-01 13:38:01 -04:00
|
|
|
|
|
tamer: asg::air::AirAggregate: Initial impl of nested exprs
This introduces a number of concepts together, again to demonstrate that
they were derived.
This introduces support for nested expressions, extending the previous
work. It also supports error recovery for dangling expressions.
The parser states are a mess; there is a lot of duplicate code here that
needs refactoring, but I wanted to commit this first at a known-good state
so that the diff will demonstrate the need for the change that will
follow; the opportunities for abstraction are plainly visible.
The immutable stack introduced here could be generalized, if needed, in the
future.
Another important note is that Rust optimizes away the `memcpy`s for the
stack that was introduced here. The initial Parser Context was introduced
because of `ArrayVec` inhibiting that elision, but Vec never had that
problem. In the future, I may choose to go back and remove ArrayVec, but I
had wanted to keep memory allocation out of the picture as much as possible
to make the disassembly and call graph easier to reason about and to have
confidence that optimizations were being performed as intended.
With that said---it _should_ be eliding in tamec, since we're not doing
anything meaningful yet with the graph. It does also elide in tameld, but
it's possible that Rust recognizes that those code paths are never taken
because tameld does nothing with expressions. So I'll have to monitor this
as I progress and adjust accordingly; it's possible a future commit will
call BS on everything I just said.
Of course, the counter-point to that is that Rust is optimizing them away
anyway, but Vec _does_ still require allocation; I was hoping to keep such
allocation at the fringes. But another counter-point is that it _still_ is
allocated at the fringe, when the context is initialized for the parser as
part of the lowering pipeline. But I didn't know how that would all come
together back then.
...alright, enough rambling.
DEV-13160
2023-01-05 15:57:06 -05:00
|
|
|
|
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."),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 16:38:59 -04:00
|
|
|
|
#[cfg(test)]
|
2023-01-17 21:57:50 -05:00
|
|
|
|
mod test;
|