tamer: asg::air: Utilize AirAggregateCtx for index lookups

This change means that `asg::air` is now the only module that directly
invokes index-related methods on `Asg`.  This clears the way, finally, to
removing the index from `Asg` entirely.

Not only does this result in a less awkward architecture, it also ensures
that lookups are forced to go through the system that understands and
controls lexical scoping, which will be able to give the correct answer.

Of course, the caveat is that the "correct" answer depends on what's
currently on the stack, depending on what type of lookup is being performed,
but those details are still encapsulated within the `asg::air` module and
its tests.

DEV-13162
main
Mike Gerwitz 2023-05-18 01:10:23 -04:00
parent 94bbc2d725
commit 8b3dfe9149
6 changed files with 232 additions and 154 deletions

View File

@ -47,6 +47,9 @@ use crate::{
};
use std::fmt::{Debug, Display};
#[cfg(test)]
use super::{graph::object::ObjectRelatable, ObjectIndexRelTo};
#[macro_use]
mod ir;
pub use ir::Air;
@ -586,7 +589,35 @@ impl AirAggregateCtx {
))
}
/// Attempt to locate a lexically scoped identifier,
/// Attempt to retrieve an identifier and its scope information from the
/// graph by name relative to the environment `env`.
///
/// See [`Self::lookup`] for more information.
#[cfg(test)]
fn env_scope_lookup_raw<O: ObjectRelatable>(
&self,
env: impl ObjectIndexRelTo<O>,
name: SPair,
) -> Option<EnvScopeKind<ObjectIndex<O>>> {
self.asg_ref().lookup_raw(env, name)
}
/// Resolve an identifier at the scope of the provided environment.
///
/// If the identifier is not in scope at `env`,
/// [`None`] will be returned.
#[cfg(test)]
fn env_scope_lookup<O: ObjectRelatable>(
&self,
env: impl ObjectIndexRelTo<O>,
name: SPair,
) -> Option<ObjectIndex<O>> {
self.env_scope_lookup_raw(env, name)
.and_then(EnvScopeKind::in_scope)
.map(EnvScopeKind::into_inner)
}
/// Attempt to locate a lexically scoped identifier in the current stack,
/// or create a new one if missing.
///
/// Since shadowing is not permitted

View File

@ -21,8 +21,8 @@ use super::*;
use crate::asg::{
air::{
test::{
asg_from_pkg_body_toks, parse_as_pkg_body, pkg_expect_ident_obj,
pkg_expect_ident_oi, pkg_lookup,
air_ctx_from_pkg_body_toks, air_ctx_from_toks, parse_as_pkg_body,
pkg_expect_ident_obj, pkg_expect_ident_oi, pkg_lookup,
},
Air, AirAggregate,
},
@ -55,14 +55,11 @@ fn expr_empty_ident() {
Air::ExprEnd(S3),
];
let mut sut = parse_as_pkg_body(toks);
assert!(sut.all(|x| x.is_ok()));
let asg = sut.finalize().unwrap().into_context();
let ctx = air_ctx_from_pkg_body_toks(toks);
// The expression should have been bound to this identifier so that
// we're able to retrieve it from the graph by name.
let expr = pkg_expect_ident_obj::<Expr>(&asg, id);
let expr = pkg_expect_ident_obj::<Expr>(&ctx, id);
assert_eq!(expr.span(), S1.merge(S3).unwrap());
}
@ -185,16 +182,13 @@ fn expr_non_empty_ident_root() {
Air::ExprEnd(S6),
];
let mut sut = parse_as_pkg_body(toks);
assert!(sut.all(|x| x.is_ok()));
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = sut.finalize().unwrap().into_context();
let expr_a = pkg_expect_ident_obj::<Expr>(&asg, id_a);
let expr_a = pkg_expect_ident_obj::<Expr>(&ctx, id_a);
assert_eq!(expr_a.span(), S1.merge(S6).unwrap());
// Identifiers should reference the same expression.
let expr_b = pkg_expect_ident_obj::<Expr>(&asg, id_b);
let expr_b = pkg_expect_ident_obj::<Expr>(&ctx, id_b);
assert_eq!(expr_a, expr_b);
}
@ -219,12 +213,9 @@ fn expr_non_empty_bind_only_after() {
Air::ExprEnd(S5),
];
let mut sut = parse_as_pkg_body(toks);
assert!(sut.all(|x| x.is_ok()));
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = sut.finalize().unwrap().into_context();
let expr = pkg_expect_ident_obj::<Expr>(&asg, id);
let expr = pkg_expect_ident_obj::<Expr>(&ctx, id);
assert_eq!(expr.span(), S1.merge(S5).unwrap());
}
@ -406,11 +397,11 @@ fn recovery_expr_reachable_after_dangling() {
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
// Let's make sure that we _actually_ added it to the graph,
// despite the previous error.
let expr = pkg_expect_ident_obj::<Expr>(&asg, id);
let expr = pkg_expect_ident_obj::<Expr>(&ctx, id);
assert_eq!(expr.span(), S3.merge(S5).unwrap());
// The dangling expression may or may not be on the graph,
@ -462,10 +453,10 @@ fn expr_close_unbalanced() {
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
// Just verify that the expression was successfully added after recovery.
let expr = pkg_expect_ident_obj::<Expr>(&asg, id);
let expr = pkg_expect_ident_obj::<Expr>(&ctx, id);
assert_eq!(expr.span(), S2.merge(S4).unwrap());
}
@ -498,14 +489,15 @@ fn sibling_subexprs_have_ordered_edges_to_parent() {
Air::ExprEnd(S9),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
// The root is the parent expression that should contain edges to each
// subexpression
// (the siblings above).
// Note that we retrieve its _index_,
// not the object itself.
let oi_root = pkg_expect_ident_oi::<Expr>(&asg, id_root);
let oi_root = pkg_expect_ident_oi::<Expr>(&ctx, id_root);
let siblings = oi_root
.edges_filtered::<Expr>(&asg)
@ -541,9 +533,10 @@ fn nested_subexprs_related_to_relative_parent() {
Air::ExprEnd(S6),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_0 = pkg_expect_ident_oi::<Expr>(&asg, id_root);
let oi_0 = pkg_expect_ident_oi::<Expr>(&ctx, id_root);
let subexprs_0 = collect_subexprs(&asg, oi_0);
// Subexpr 1
@ -598,10 +591,10 @@ fn expr_redefine_ident() {
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
// The identifier should continue to reference the first expression.
let expr = pkg_expect_ident_obj::<Expr>(&asg, id_first);
let expr = pkg_expect_ident_obj::<Expr>(&ctx, id_first);
assert_eq!(expr.span(), S1.merge(S5).unwrap());
}
@ -681,10 +674,10 @@ fn expr_still_dangling_on_redefine() {
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
// The identifier should continue to reference the first expression.
let expr = pkg_expect_ident_obj::<Expr>(&asg, id_first);
let expr = pkg_expect_ident_obj::<Expr>(&ctx, id_first);
assert_eq!(expr.span(), S1.merge(S3).unwrap());
// There's nothing we can do using the ASG's public API at the time of
@ -692,7 +685,7 @@ fn expr_still_dangling_on_redefine() {
// The second identifier should have been successfully bound despite the
// failed initial attempt.
let expr = pkg_expect_ident_obj::<Expr>(&asg, id_second);
let expr = pkg_expect_ident_obj::<Expr>(&ctx, id_second);
assert_eq!(expr.span(), S7.merge(S10).unwrap());
}
@ -721,9 +714,10 @@ fn expr_ref_to_ident() {
Air::ExprEnd(S7),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_foo = pkg_expect_ident_oi::<Expr>(&asg, id_foo);
let oi_foo = pkg_expect_ident_oi::<Expr>(&ctx, id_foo);
let mut foo_rels = oi_foo
.edges(&asg)
@ -748,7 +742,7 @@ fn expr_ref_to_ident() {
// added it as `Missing`.
assert_eq!(ident_bar.span(), id_bar.span());
let oi_expr_bar = pkg_expect_ident_oi::<Expr>(&asg, id_bar);
let oi_expr_bar = pkg_expect_ident_oi::<Expr>(&ctx, id_bar);
assert!(oi_ident_bar.is_bound_to(&asg, oi_expr_bar));
}
@ -773,24 +767,23 @@ fn idents_share_defining_pkg() {
Air::PkgEnd(S9),
];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
let asg = sut.finalize().unwrap().into_context();
let ctx = air_ctx_from_toks(toks);
let asg = ctx.asg_ref();
let oi_foo = pkg_lookup(&asg, id_foo).unwrap();
let oi_bar = pkg_lookup(&asg, id_bar).unwrap();
let oi_foo = pkg_lookup(&ctx, id_foo).unwrap();
let oi_bar = pkg_lookup(&ctx, id_bar).unwrap();
assert_eq!(oi_foo.src_pkg(&asg).unwrap(), oi_bar.src_pkg(&asg).unwrap());
assert_eq!(oi_foo.src_pkg(asg).unwrap(), oi_bar.src_pkg(asg).unwrap());
// Missing identifiers should not have a source package,
// since we don't know what defined it yet.
let oi_baz = pkg_lookup(&asg, id_baz).unwrap();
assert_eq!(None, oi_baz.src_pkg(&asg));
let oi_baz = pkg_lookup(&ctx, id_baz).unwrap();
assert_eq!(None, oi_baz.src_pkg(asg));
// The package span should encompass the entire definition.
assert_eq!(
S1.merge(S9),
oi_foo.src_pkg(&asg).map(|pkg| pkg.resolve(&asg).span())
oi_foo.src_pkg(asg).map(|pkg| pkg.resolve(asg).span())
)
}
@ -807,9 +800,10 @@ fn expr_doc_short_desc() {
Air::ExprEnd(S4),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_expr = pkg_expect_ident_oi::<Expr>(&asg, id_expr);
let oi_expr = pkg_expect_ident_oi::<Expr>(&ctx, id_expr);
let oi_docs = oi_expr
.edges_filtered::<Doc>(&asg)
.map(ObjectIndex::cresolve(&asg));

View File

@ -72,11 +72,11 @@ fn ident_decl() {
sut.by_ref().collect::<Vec<Result<Parsed<()>, _>>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let ident = asg.get(ident_node).unwrap();
root_lookup(&ctx, id).expect("identifier was not added to graph");
let ident = ctx.asg_ref().get(ident_node).unwrap();
assert_eq!(
Ok(ident),
@ -127,11 +127,11 @@ fn ident_extern_decl() {
sut.by_ref().collect::<Vec<Result<Parsed<()>, _>>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let ident = asg.get(ident_node).unwrap();
root_lookup(&ctx, id).expect("identifier was not added to graph");
let ident = ctx.asg_ref().get(ident_node).unwrap();
assert_eq!(
Ok(ident),
@ -163,13 +163,13 @@ fn ident_dep() {
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let dep_node = root_lookup(&asg, dep).expect("dep was not added to graph");
root_lookup(&ctx, id).expect("identifier was not added to graph");
let dep_node = root_lookup(&ctx, dep).expect("dep was not added to graph");
assert!(ident_node.has_edge_to(&asg, dep_node));
assert!(ident_node.has_edge_to(ctx.asg_ref(), dep_node));
}
#[test]
@ -213,11 +213,11 @@ fn ident_fragment() {
sut.by_ref().collect::<Vec<Result<Parsed<()>, _>>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to graph");
let ident = asg.get(ident_node).unwrap();
root_lookup(&ctx, id).expect("identifier was not added to graph");
let ident = ctx.asg_ref().get(ident_node).unwrap();
assert_eq!(
Ok(ident),
@ -253,18 +253,18 @@ fn ident_root_missing() {
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to the graph");
let ident = asg.get(ident_node).unwrap();
root_lookup(&ctx, id).expect("identifier was not added to the graph");
let ident = ctx.asg_ref().get(ident_node).unwrap();
// The identifier did not previously exist,
// and so a missing node is created as a placeholder.
assert_eq!(&Ident::Missing(id), ident);
// And that missing identifier should be rooted.
assert!(ident_node.is_rooted(&asg));
assert!(ident_node.is_rooted(ctx.asg_ref()));
}
#[test]
@ -302,11 +302,11 @@ fn ident_root_existing() {
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
let ident_node =
root_lookup(&asg, id).expect("identifier was not added to the graph");
let ident = asg.get(ident_node).unwrap();
root_lookup(&ctx, id).expect("identifier was not added to the graph");
let ident = ctx.asg_ref().get(ident_node).unwrap();
// The previously-declared identifier...
assert_eq!(
@ -317,7 +317,7 @@ fn ident_root_existing() {
);
// ...should have been subsequently rooted.
assert!(ident_node.is_rooted(&asg));
assert!(ident_node.is_rooted(ctx.asg_ref()));
}
#[test]
@ -360,13 +360,13 @@ fn declare_kind_auto_root() {
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
let oi_auto = root_lookup(&asg, id_auto).unwrap();
let oi_no_auto = root_lookup(&asg, id_no_auto).unwrap();
let oi_auto = root_lookup(&ctx, id_auto).unwrap();
let oi_no_auto = root_lookup(&ctx, id_no_auto).unwrap();
assert!(oi_auto.is_rooted(&asg));
assert!(!oi_no_auto.is_rooted(&asg));
assert!(oi_auto.is_rooted(ctx.asg_ref()));
assert!(!oi_no_auto.is_rooted(ctx.asg_ref()));
}
#[test]
@ -450,21 +450,21 @@ fn pkg_canonical_name() {
PkgEnd(S3),
];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
let ctx = air_ctx_from_toks(toks);
let asg = sut.finalize().unwrap().into_context();
let oi_root = asg.root(S1);
let oi_root = ctx.asg_ref().root(S1);
let oi_pkg = oi_root
.edges_filtered::<Pkg>(&asg)
.edges_filtered::<Pkg>(ctx.asg_ref())
.next()
.expect("cannot find package from root");
assert_eq!(name, oi_pkg.resolve(&asg).canonical_name());
assert_eq!(name, oi_pkg.resolve(ctx.asg_ref()).canonical_name());
// We should be able to find the same package by its index.
let oi_pkg_indexed = asg.lookup(oi_root, name);
let oi_pkg_indexed = ctx
.env_scope_lookup_raw(oi_root, name)
.map(|eoi| eoi.into_inner());
assert_eq!(
Some(oi_pkg),
oi_pkg_indexed,
@ -513,14 +513,16 @@ fn pkg_cannot_redeclare() {
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
// The second package should be available under the recovery name.
let oi_root = asg.root(S1);
let oi_pkg = asg
.lookup::<Pkg>(oi_root, namefix)
let oi_root = ctx.asg_ref().root(S1);
let oi_pkg = ctx
.env_scope_lookup_raw::<Pkg>(oi_root, namefix)
.map(|eoi| eoi.into_inner())
.expect("failed to locate package by its recovery name");
assert_eq!(S6.merge(S8).unwrap(), oi_pkg.resolve(&asg).span());
assert_eq!(S6.merge(S8).unwrap(), oi_pkg.resolve(ctx.asg_ref()).span());
}
#[test]
@ -638,48 +640,77 @@ where
pub(super) fn asg_from_pkg_body_toks<I: IntoIterator<Item = Air>>(
toks: I,
) -> Asg
where
I::IntoIter: Debug,
{
// Equivalent to `into_{private_=>}context` in this function.
air_ctx_from_pkg_body_toks(toks).into()
}
pub(super) fn air_ctx_from_pkg_body_toks<I: IntoIterator<Item = Air>>(
toks: I,
) -> <Sut as ParseState>::Context
where
I::IntoIter: Debug,
{
let mut sut = parse_as_pkg_body(toks);
assert!(sut.all(|x| x.is_ok()));
sut.finalize().unwrap().into_context()
sut.finalize().unwrap().into_private_context()
}
/// Create and yield a new [`Asg`] from an [`Air`] token stream.
pub fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
where
I::IntoIter: Debug,
{
// Equivalent to `into_{private_=>}context` in this function.
air_ctx_from_toks(toks).into()
}
/// Create and yield a new [`Asg`] from an [`Air`] token stream.
pub(super) fn air_ctx_from_toks<I: IntoIterator<Item = Air>>(
toks: I,
) -> <Sut as ParseState>::Context
where
I::IntoIter: Debug,
{
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
sut.finalize().unwrap().into_context()
sut.finalize().unwrap().into_private_context()
}
fn root_lookup(asg: &Asg, name: SPair) -> Option<ObjectIndex<Ident>> {
asg.lookup(asg.root(S1), name)
fn root_lookup(
ctx: &<AirAggregate as ParseState>::Context,
name: SPair,
) -> Option<ObjectIndex<Ident>> {
ctx.env_scope_lookup_raw(ctx.asg_ref().root(S1), name)
.map(|eoi| eoi.into_inner())
}
pub fn pkg_lookup(asg: &Asg, name: SPair) -> Option<ObjectIndex<Ident>> {
let oi_pkg = asg
pub fn pkg_lookup(
ctx: &<AirAggregate as ParseState>::Context,
name: SPair,
) -> Option<ObjectIndex<Ident>> {
let oi_pkg = ctx
.asg_ref()
.root(S1)
.edges_filtered::<Pkg>(&asg)
.edges_filtered::<Pkg>(ctx.asg_ref())
.next()
.expect("missing rooted package");
asg.lookup(oi_pkg, name)
ctx.env_scope_lookup(oi_pkg, name)
}
pub fn pkg_expect_ident_oi<O: ObjectRelatable + ObjectRelFrom<Ident>>(
asg: &Asg,
ctx: &<AirAggregate as ParseState>::Context,
name: SPair,
) -> ObjectIndex<O> {
// Duplicates logic of `pkg_get_ident_oi`,
// but in doing so,
// provides better assertion messages.
pkg_lookup(asg, name)
pkg_lookup(ctx, name)
.expect(&format!("missing ident: `{name}`"))
.edges(asg)
.edges(ctx.asg_ref())
.next()
.expect(&format!("missing definition for ident `{name}`"))
.narrow()
@ -687,8 +718,8 @@ pub fn pkg_expect_ident_oi<O: ObjectRelatable + ObjectRelFrom<Ident>>(
}
pub fn pkg_expect_ident_obj<O: ObjectRelatable + ObjectRelFrom<Ident>>(
asg: &Asg,
ctx: &<AirAggregate as ParseState>::Context,
name: SPair,
) -> &O {
pkg_expect_ident_oi(asg, name).resolve(asg)
pkg_expect_ident_oi(ctx, name).resolve(ctx.asg_ref())
}

View File

@ -83,9 +83,9 @@ macro_rules! test_scopes {
#[test]
fn $name() {
$($setup)*
let asg = asg_from_toks($toks);
let ctx = air_ctx_from_toks($toks);
let given = derive_scopes_from_asg(&asg, $name);
let given = derive_scopes_from_asg(&ctx, $name);
let expected = [
$( (ObjectTy::$obj, $span, $kind(())), )*
];
@ -387,7 +387,7 @@ test_scopes! {
/// which make it easy to form and prove hypotheses about the behavior of
/// TAMER's scoping system.
fn derive_scopes_from_asg<'a>(
asg: &'a Asg,
ctx: &'a <AirAggregate as ParseState>::Context,
name: SPair,
) -> impl Iterator<Item = (ObjectTy, Span, EnvScopeKind<()>)> + 'a {
// We are interested only in identifiers for scoping,
@ -419,25 +419,26 @@ fn derive_scopes_from_asg<'a>(
// but that is okay;
// it'll result in a test failure that should be easy enough to
// understand.
let given_without_root =
tree_reconstruction(asg).filter_map(move |TreeWalkRel(dynrel, _)| {
let given_without_root = tree_reconstruction(ctx.asg_ref()).filter_map(
move |TreeWalkRel(dynrel, _)| {
dynrel.target_oi_rel_to_dyn::<object::Ident>().map(|oi_to| {
(
dynrel.target_ty(),
dynrel.target().resolve(asg).span(),
asg.lookup_raw(oi_to, name),
dynrel.target().resolve(ctx.asg_ref()).span(),
ctx.env_scope_lookup_raw(oi_to, name),
)
})
});
},
);
// `tree_reconstruction` omits root,
// so we'll have to add it ourselves.
let oi_root = asg.root(name);
let oi_root = ctx.asg_ref().root(name);
once((ObjectTy::Root, S0, asg.lookup_raw(oi_root, name)))
once((ObjectTy::Root, S0, ctx.env_scope_lookup_raw(oi_root, name)))
.chain(given_without_root)
.filter_map(|(ty, span, oeoi)| {
oeoi.map(|eoi| (ty, span, eoi.map(ObjectIndex::cresolve(asg))))
oeoi.map(|eoi| (ty, span, eoi.map(ObjectIndex::cresolve(ctx.asg_ref()))))
})
.inspect(move |(ty, span, eid)| assert_eq!(
expected_span,

View File

@ -22,18 +22,16 @@ use crate::asg::{
air::{
expr::test::collect_subexprs,
test::{
asg_from_pkg_body_toks, parse_as_pkg_body, pkg_expect_ident_obj,
pkg_expect_ident_oi, pkg_lookup,
air_ctx_from_pkg_body_toks, air_ctx_from_toks, parse_as_pkg_body,
pkg_expect_ident_obj, pkg_expect_ident_oi, pkg_lookup,
},
Air, AirAggregate,
Air,
},
graph::object::{Doc, Meta, ObjectRel},
Expr, ExprOp, Ident,
};
use crate::span::dummy::*;
type Sut = AirAggregate;
// A template is defined by the package containing it,
// like an expression.
#[test]
@ -50,14 +48,13 @@ fn tpl_defining_pkg() {
Air::PkgEnd(S5),
];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
let asg = sut.finalize().unwrap().into_context();
let ctx = air_ctx_from_toks(toks);
let asg = ctx.asg_ref();
let tpl = pkg_expect_ident_obj::<Tpl>(&asg, id_tpl);
let tpl = pkg_expect_ident_obj::<Tpl>(&ctx, id_tpl);
assert_eq!(S2.merge(S4).unwrap(), tpl.span());
let oi_id_tpl = pkg_lookup(&asg, id_tpl).unwrap();
let oi_id_tpl = pkg_lookup(&ctx, id_tpl).unwrap();
assert_eq!(
S1.merge(S5),
oi_id_tpl.src_pkg(&asg).map(|pkg| pkg.resolve(&asg).span()),
@ -85,11 +82,9 @@ fn tpl_after_expr() {
Air::PkgEnd(S8),
];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
let asg = sut.finalize().unwrap().into_context();
let ctx = air_ctx_from_toks(toks);
let tpl = pkg_expect_ident_obj::<Tpl>(&asg, id_tpl);
let tpl = pkg_expect_ident_obj::<Tpl>(&ctx, id_tpl);
assert_eq!(S5.merge(S7).unwrap(), tpl.span());
}
@ -133,18 +128,17 @@ fn tpl_within_expr() {
Air::PkgEnd(S12),
];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
let asg = sut.finalize().unwrap().into_context();
let ctx = air_ctx_from_toks(toks);
let asg = ctx.asg_ref();
// The inner template.
let tpl = pkg_expect_ident_obj::<Tpl>(&asg, id_tpl);
let tpl = pkg_expect_ident_obj::<Tpl>(&ctx, id_tpl);
assert_eq!(S6.merge(S8).unwrap(), tpl.span());
// The expression that was produced on the graph ought to be equivalent
// to the expression without the template being present at all
// (noting that the spans are of course not adjusted).
let oi_expr = pkg_expect_ident_oi::<Expr>(&asg, id_expr);
let oi_expr = pkg_expect_ident_oi::<Expr>(&ctx, id_expr);
let expr = oi_expr.resolve(&asg);
assert_eq!(S2.merge(S11).unwrap(), expr.span());
assert_eq!(
@ -187,16 +181,17 @@ fn tpl_apply_within_expr() {
Air::ExprEnd(S10),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
// The inner template.
let tpl = pkg_expect_ident_obj::<Tpl>(&asg, id_tpl);
let tpl = pkg_expect_ident_obj::<Tpl>(&ctx, id_tpl);
assert_eq!(S4.merge(S6).unwrap(), tpl.span());
// The expression that was produced on the graph ought to be equivalent
// to the expression without the template being present at all,
// but retaining the _application_.
let oi_expr = pkg_expect_ident_oi::<Expr>(&asg, id_expr);
let oi_expr = pkg_expect_ident_oi::<Expr>(&ctx, id_expr);
let expr = oi_expr.resolve(&asg);
assert_eq!(S2.merge(S10).unwrap(), expr.span());
assert_eq!(
@ -262,9 +257,10 @@ fn tpl_with_reachable_expression() {
Air::TplEnd(S9),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&asg, id_tpl);
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl);
let tpl = oi_tpl.resolve(&asg);
assert_eq!(S1.merge(S9).unwrap(), tpl.span());
@ -293,19 +289,19 @@ fn tpl_with_reachable_expression() {
);
// ...but not by the package containing the template.
let oi_pkg = pkg_lookup(&asg, id_tpl).unwrap().src_pkg(&asg).unwrap();
let oi_pkg = pkg_lookup(&ctx, id_tpl).unwrap().src_pkg(&asg).unwrap();
assert_eq!(
vec![
// The only identifier on the package should be the template itself.
pkg_lookup(&asg, id_tpl).unwrap(),
pkg_lookup(&ctx, id_tpl).unwrap(),
],
oi_pkg.edges_filtered::<Ident>(&asg).collect::<Vec<_>>()
);
// Verify the above claim that these are not cached in the global
// environment.
assert_eq!(None, pkg_lookup(&asg, id_expr_a));
assert_eq!(None, pkg_lookup(&asg, id_expr_b));
assert_eq!(None, pkg_lookup(&ctx, id_expr_a));
assert_eq!(None, pkg_lookup(&ctx, id_expr_b));
}
// Templates can expand into many contexts,
@ -332,8 +328,10 @@ fn tpl_holds_dangling_expressions() {
Air::TplEnd(S7),
];
let asg = asg_from_pkg_body_toks(toks);
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&asg, id_tpl);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl);
assert_eq!(
vec![S5.merge(S6).unwrap(), S3.merge(S4).unwrap(),],
@ -430,10 +428,10 @@ fn unreachable_anonymous_tpl() {
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
let ctx = sut.finalize().unwrap().into_private_context();
// Let's make sure that the template created after recovery succeeded.
pkg_expect_ident_obj::<Tpl>(&asg, id_ok);
pkg_expect_ident_obj::<Tpl>(&ctx, id_ok);
}
// Normally we cannot reference objects without an identifier using AIR
@ -483,8 +481,10 @@ fn tpl_with_param() {
Air::TplEnd(S10),
];
let asg = asg_from_pkg_body_toks(toks);
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&asg, id_tpl);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl);
// The template should have an edge to each identifier for each
// metavariable.
@ -524,13 +524,14 @@ fn tpl_nested() {
Air::TplEnd(S6),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
// The outer template should be defined globally,
// but not the inner,
// since it hasn't been expanded yet.
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&asg, id_tpl_outer);
assert_eq!(None, pkg_lookup(&asg, id_tpl_inner));
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl_outer);
assert_eq!(None, pkg_lookup(&ctx, id_tpl_inner));
assert_eq!(S1.merge(S6).unwrap(), oi_tpl_outer.resolve(&asg).span());
// The identifier for the inner template should be local to the outer
@ -567,9 +568,10 @@ fn tpl_apply_nested() {
Air::TplEnd(S5),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&asg, id_tpl_outer);
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl_outer);
assert_eq!(S1.merge(S5).unwrap(), oi_tpl_outer.resolve(&asg).span());
// The inner template,
@ -616,14 +618,15 @@ fn tpl_apply_nested_missing() {
Air::TplEnd(S12),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&asg, id_tpl_outer);
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl_outer);
assert_eq!(S1.merge(S12).unwrap(), oi_tpl_outer.resolve(&asg).span());
// The inner template should be contained within the outer and so not
// globally resolvable.
assert!(pkg_lookup(&asg, id_tpl_inner).is_none());
assert!(pkg_lookup(&ctx, id_tpl_inner).is_none());
// But it is accessible as a local on the outer template.
let oi_tpl_inner = oi_tpl_outer
@ -668,9 +671,10 @@ fn tpl_doc_short_desc() {
Air::TplEnd(S4),
];
let asg = asg_from_pkg_body_toks(toks);
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_expr = pkg_expect_ident_oi::<Tpl>(&asg, id_tpl);
let oi_expr = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl);
let oi_docs = oi_expr
.edges_filtered::<Doc>(&asg)
.map(ObjectIndex::cresolve(&asg));

View File

@ -496,12 +496,29 @@ where
pub struct FinalizedParser<S: ParseState>(S::Context);
impl<S: ParseState> FinalizedParser<S> {
/// Take ownership over the inner [`ParseState::Context`].
/// Take ownership over the inner [`ParseState::Context`],
/// converting it into its public representation.
///
/// This represents,
/// in essence,
/// a final aggregate return value of the parser.
pub fn into_context(self) -> S::PubContext {
match self {
Self(ctx) => ctx.into(),
}
}
/// Take ownership over the inner [`ParseState::Context`].
///
/// It is expected that `S::Context` is not suitable for consumption
/// outside of the module that defined `S`.
/// [`Self::into_context`] should be used as a public API.
#[cfg(test)]
pub fn into_private_context(self) -> S::Context {
match self {
Self(ctx) => ctx.into(),
}
}
}
#[cfg(test)]