diff --git a/tamer/src/asg/air/tpl/test.rs b/tamer/src/asg/air/tpl/test.rs
index 4af29492..c6c74f1b 100644
--- a/tamer/src/asg/air/tpl/test.rs
+++ b/tamer/src/asg/air/tpl/test.rs
@@ -17,6 +17,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+mod apply;
+
use super::*;
use crate::asg::air::test::{as_pkg_body, Sut};
use crate::span::dummy::*;
@@ -163,57 +165,6 @@ fn tpl_within_expr() {
);
}
-// Like the above test,
-// but now we're _applying_ a template.
-#[test]
-fn tpl_apply_within_expr() {
- let id_expr = spair("expr", S3);
- let id_tpl = spair("_tpl_", S5);
- let ref_tpl = spair("_tpl_", S8);
-
- #[rustfmt::skip]
- let toks = [
- ExprStart(ExprOp::Sum, S2),
- BindIdent(id_expr),
-
- // This will not be present in the final expression,
- // as if it were hoisted out.
- TplStart(S4),
- BindIdent(id_tpl),
- TplEnd(S6),
-
- // But the application will remain.
- TplStart(S7),
- RefIdent(ref_tpl),
- TplEndRef(S9),
- ExprEnd(S10),
- ];
-
- let ctx = air_ctx_from_pkg_body_toks(toks);
- let asg = ctx.asg_ref();
-
- let tpl = pkg_expect_ident_obj::(&ctx, id_tpl);
- assert_eq!(S4.merge(S6).unwrap(), tpl.span());
- assert_eq!(TplShape::Empty, tpl.shape());
-
- // 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::(&ctx, id_expr);
- let expr = oi_expr.resolve(&asg);
- assert_eq!(S2.merge(S10).unwrap(), expr.span());
- assert_eq!(
- #[rustfmt::skip]
- vec![
- S7.merge(S9).unwrap(),
- ],
- oi_expr
- .edges(&asg)
- .map(|rel| rel.widen().resolve(&asg).span())
- .collect::>()
- );
-}
-
#[test]
fn close_tpl_without_open() {
let id_tpl = spair("_tpl_", S3);
@@ -534,27 +485,6 @@ fn unreachable_anonymous_tpl() {
pkg_expect_ident_obj::(&ctx, id_ok);
}
-// Normally we cannot reference objects without an identifier using AIR
-// (at the time of writing at least),
-// but `TplEndRef` is an exception.
-#[test]
-fn anonymous_tpl_immediate_ref() {
- #[rustfmt::skip]
- let toks = [
- TplStart(S1),
- // No BindIdent
- // But ended with `TplEndRef`,
- // so the missing identifier is okay.
- // This would fail if it were `TplEnd`.
- TplEndRef(S2),
- ];
-
- let mut sut = parse_as_pkg_body(toks);
- assert!(sut.all(|x| x.is_ok()));
-
- // TODO: More to come.
-}
-
#[test]
fn tpl_with_param() {
let id_tpl = spair("_tpl_", S2);
@@ -678,298 +608,6 @@ fn tpl_nested() {
assert_eq!(TplShape::Empty, tpl_outer.shape());
}
-// A template application within another template can be interpreted as
-// either applying the template as much as possible into the body of the
-// template definition,
-// or as expanding the template application into the expansion site and
-// _then_ expanding the inner template at the expansion site.
-// Both will yield equivalent results,
-// but in either case,
-// it all starts the same.
-#[test]
-fn tpl_apply_nested() {
- let id_tpl_outer = spair("_tpl-outer_", S2);
-
- #[rustfmt::skip]
- let toks = [
- TplStart(S1),
- BindIdent(id_tpl_outer),
-
- // Inner template application
- TplStart(S3),
- TplEndRef(S4),
- TplEnd(S5),
- ];
-
- let ctx = air_ctx_from_pkg_body_toks(toks);
- let asg = ctx.asg_ref();
-
- let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
- assert_eq!(S1.merge(S5).unwrap(), oi_tpl_outer.resolve(&asg).span());
-
- // The inner template,
- // being a template application,
- // should be a direct child of the outer template.
- let inners = oi_tpl_outer
- .edges_filtered::(&asg)
- .map(|oi| oi.resolve(&asg).span());
-
- assert_eq!(vec![S3.merge(S4).unwrap()], inners.collect::>());
-
- // Since the inner template is empty,
- // so too should the outer.
- let tpl_outer = oi_tpl_outer.resolve(&asg);
- assert_eq!(TplShape::Empty, tpl_outer.shape());
-}
-
-// Template application should resolve all the same regardless of order of
-// ref/def.
-#[test]
-fn tpl_apply_nested_missing() {
- let id_tpl_outer = spair("_tpl-outer_", S2);
-
- let tpl_inner = "_tpl-inner_";
- let id_tpl_inner = spair(tpl_inner, S7);
- let ref_tpl_inner_pre = spair(tpl_inner, S4);
- let ref_tpl_inner_post = spair(tpl_inner, S10);
-
- #[rustfmt::skip]
- let toks = [
- TplStart(S1),
- BindIdent(id_tpl_outer),
-
- // Inner template application (Missing)
- TplStart(S3),
- RefIdent(ref_tpl_inner_pre),
- TplEndRef(S5),
-
- // Define the template above
- TplStart(S6),
- BindIdent(id_tpl_inner),
- TplEnd(S8),
-
- // Apply again,
- // this time _after_ having been defined.
- TplStart(S9),
- RefIdent(ref_tpl_inner_post),
- TplEndRef(S11),
- TplEnd(S12),
- ];
-
- let ctx = air_ctx_from_pkg_body_toks(toks);
- let asg = ctx.asg_ref();
-
- let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
- assert_eq!(S1.merge(S12).unwrap(), oi_tpl_outer.resolve(&asg).span());
-
- // We apply two template,
- // both of which are empty,
- // and so the outer shape is still empty.
- let tpl_outer = oi_tpl_outer.resolve(&asg);
- assert_eq!(TplShape::Empty, tpl_outer.shape());
-
- // The inner template should be contained within the outer and so not
- // globally resolvable.
- 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
- .lookup_local_linear(&asg, id_tpl_inner)
- .expect("could not locate inner template as a local")
- .definition::(&asg)
- .expect("could not resolve inner template ref to Tpl");
-
- // We should have two inner template applications.
- let inners = oi_tpl_outer.edges_filtered::(&asg).collect::>();
- assert_eq!(
- vec![S9.merge(S11).unwrap(), S3.merge(S5).unwrap()],
- inners
- .iter()
- .map(|oi| oi.resolve(&asg).span())
- .collect::>(),
- );
-
- // Each of those inner template applications should have resolved to the
- // same template,
- // despite their varying ref/def ordering.
- assert_eq!(
- vec![oi_tpl_inner, oi_tpl_inner],
- inners
- .iter()
- .flat_map(|oi| oi.edges_filtered::(&asg))
- .filter_map(|oi| oi.definition(&asg))
- .collect::>(),
- );
-}
-
-#[test]
-fn tpl_inner_apply_inherit_shape() {
- let id_tpl_outer = spair("_tpl-outer_", S2);
-
- #[rustfmt::skip]
- let toks = [
- TplStart(S1),
- BindIdent(id_tpl_outer),
-
- // Inner template application has an Expr shape.
- TplStart(S3),
- ExprStart(ExprOp::Sum, S4),
- ExprEnd(S5),
- TplEndRef(S6),
- TplEnd(S7),
- ];
-
- let ctx = air_ctx_from_pkg_body_toks(toks);
- let asg = ctx.asg_ref();
-
- let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
- let oi_tpl_inner = oi_tpl_outer.edges_filtered::(&asg).next().unwrap();
-
- // The inner template's expression should determine its shape.
- assert_eq!(
- TplShape::Expr(S4.merge(S5).unwrap()),
- oi_tpl_inner.resolve(&asg).shape()
- );
-
- // Since the outer template applies the inner,
- // it inherits the inner's shape.
- // (Imagine pasting the body of the inner template inline.)
- assert_eq!(
- TplShape::Expr(S3.merge(S6).unwrap()),
- oi_tpl_outer.resolve(&asg).shape()
- );
-}
-
-// It's okay to have sibling template applications that aren't Exprs.
-#[test]
-fn tpl_inner_apply_expr_alongside_empty() {
- let id_tpl_outer = spair("_tpl-outer_", S2);
-
- #[rustfmt::skip]
- let toks = [
- TplStart(S1),
- BindIdent(id_tpl_outer),
-
- // Inner template application has an Expr shape.
- TplStart(S3),
- ExprStart(ExprOp::Sum, S4),
- ExprEnd(S5),
- TplEndRef(S6),
-
- // But this is empty.
- TplStart(S7),
- TplEndRef(S8),
- TplEnd(S9),
- ];
-
- let ctx = air_ctx_from_pkg_body_toks(toks);
- let asg = ctx.asg_ref();
-
- let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
-
- // The Expr shape takes precedence.
- assert_eq!(
- TplShape::Expr(S3.merge(S6).unwrap()),
- oi_tpl_outer.resolve(&asg).shape()
- );
-}
-
-// Template applications with Expr shapes yield the same errors as if the
-// bodies were just pasted inline,
-// with the exception of the spans that are reported.
-// The spans we provide to the user should be in the context of the template
-// being defined;
-// if we inherit the shape from the template,
-// then the span would be for the expression _inside_ that template,
-// which would compose to the innermost application's expression,
-// recursively.
-// And while that would certainly make for an impressive display if you
-// understood what was going on,
-// that breaks every layer of abstraction that was built and is not what
-// we want to provide.
-#[test]
-fn tpl_inner_apply_expr_alongside_another_apply_expr() {
- let id_tpl_outer = spair("_tpl-outer_", S2);
-
- #[rustfmt::skip]
- let toks = [
- TplStart(S1),
- BindIdent(id_tpl_outer),
-
- // Inner template application has an Expr shape.
- TplStart(S3),
- ExprStart(ExprOp::Sum, S4),
- ExprEnd(S5),
- TplEndRef(S6),
-
- // As does this one,
- // which should produce an error.
- TplStart(S7),
- ExprStart(ExprOp::Sum, S8),
- ExprEnd(S9),
- TplEndRef(S10),
- TplEnd(S11),
- ];
-
- let mut sut = Sut::parse(as_pkg_body(toks));
-
- assert_eq!(
- #[rustfmt::skip]
- vec![
- Ok(Parsed::Incomplete), // PkgStart
- Ok(Parsed::Incomplete), // TplStart
- Ok(Parsed::Incomplete), // BindIdent
-
- // This one's okay and changes the template's shape.
- Ok(Parsed::Incomplete), // TplStart
- Ok(Parsed::Incomplete), // ExprStart
- Ok(Parsed::Incomplete), // ExprEnd
- Ok(Parsed::Incomplete), // TplEnd
-
- // But this one runs afoul of that new shape.
- Ok(Parsed::Incomplete), // TplStart
- Ok(Parsed::Incomplete), // ExprStart
- Ok(Parsed::Incomplete), // ExprEnd
- Err(ParseError::StateError(
- // These spans are relative to the application itself.
- // This is important to present the appropriate level of
- // abstraction;
- // see the comment for this test case.
- AsgError::TplShapeExprMulti(
- Some(id_tpl_outer),
- S7.merge(S10).unwrap(),
- S3.merge(S6).unwrap()
- )
- )),
- // RECOVERY: We ignore the template by not adding the edge.
- Ok(Parsed::Incomplete), // TplEnd >LA
- Ok(Parsed::Incomplete), // TplEnd >(),
- );
-
- let ctx = sut.finalize().unwrap().into_private_context();
- let asg = ctx.asg_ref();
-
- let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
-
- assert_eq!(
- TplShape::Expr(S3.merge(S6).unwrap()),
- oi_tpl_outer.resolve(&asg).shape()
- );
-
- // The second template application should have been omitted.
- assert_eq!(
- vec![S3.merge(S6).unwrap()],
- oi_tpl_outer
- .edges_filtered::(&asg)
- .map(ObjectIndex::cresolve(&asg))
- .map(Tpl::span)
- .collect::>()
- );
-}
-
#[test]
fn tpl_doc_short_desc() {
let id_tpl = spair("foo", S2);
diff --git a/tamer/src/asg/air/tpl/test/apply.rs b/tamer/src/asg/air/tpl/test/apply.rs
new file mode 100644
index 00000000..3b9c5db8
--- /dev/null
+++ b/tamer/src/asg/air/tpl/test/apply.rs
@@ -0,0 +1,380 @@
+// Tests for AIR template application
+//
+// Copyright (C) 2014-2023 Ryan Specialty, LLC.
+//
+// This file is part of TAME.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::*;
+
+#[test]
+fn tpl_apply_within_expr() {
+ let id_expr = spair("expr", S3);
+ let id_tpl = spair("_tpl_", S5);
+ let ref_tpl = spair("_tpl_", S8);
+
+ #[rustfmt::skip]
+ let toks = [
+ ExprStart(ExprOp::Sum, S2),
+ BindIdent(id_expr),
+
+ // This will not be present in the final expression,
+ // as if it were hoisted out.
+ TplStart(S4),
+ BindIdent(id_tpl),
+ TplEnd(S6),
+
+ // But the application will remain.
+ TplStart(S7),
+ RefIdent(ref_tpl),
+ TplEndRef(S9),
+ ExprEnd(S10),
+ ];
+
+ let ctx = air_ctx_from_pkg_body_toks(toks);
+ let asg = ctx.asg_ref();
+
+ let tpl = pkg_expect_ident_obj::(&ctx, id_tpl);
+ assert_eq!(S4.merge(S6).unwrap(), tpl.span());
+ assert_eq!(TplShape::Empty, tpl.shape());
+
+ // 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::(&ctx, id_expr);
+ let expr = oi_expr.resolve(&asg);
+ assert_eq!(S2.merge(S10).unwrap(), expr.span());
+ assert_eq!(
+ #[rustfmt::skip]
+ vec![
+ S7.merge(S9).unwrap(),
+ ],
+ oi_expr
+ .edges(&asg)
+ .map(|rel| rel.widen().resolve(&asg).span())
+ .collect::>()
+ );
+}
+
+// Normally we cannot reference objects without an identifier using AIR
+// (at the time of writing at least),
+// but `TplEndRef` is an exception.
+#[test]
+fn anonymous_tpl_immediate_ref() {
+ #[rustfmt::skip]
+ let toks = [
+ TplStart(S1),
+ // No BindIdent
+ // But ended with `TplEndRef`,
+ // so the missing identifier is okay.
+ // This would fail if it were `TplEnd`.
+ TplEndRef(S2),
+ ];
+
+ let mut sut = parse_as_pkg_body(toks);
+ assert!(sut.all(|x| x.is_ok()));
+}
+
+// A template application within another template can be interpreted as
+// either applying the template as much as possible into the body of the
+// template definition,
+// or as expanding the template application into the expansion site and
+// _then_ expanding the inner template at the expansion site.
+// Both will yield equivalent results,
+// but in either case,
+// it all starts the same.
+#[test]
+fn tpl_apply_nested() {
+ let id_tpl_outer = spair("_tpl-outer_", S2);
+
+ #[rustfmt::skip]
+ let toks = [
+ TplStart(S1),
+ BindIdent(id_tpl_outer),
+
+ // Inner template application
+ TplStart(S3),
+ TplEndRef(S4),
+ TplEnd(S5),
+ ];
+
+ let ctx = air_ctx_from_pkg_body_toks(toks);
+ let asg = ctx.asg_ref();
+
+ let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
+ assert_eq!(S1.merge(S5).unwrap(), oi_tpl_outer.resolve(&asg).span());
+
+ // The inner template,
+ // being a template application,
+ // should be a direct child of the outer template.
+ let inners = oi_tpl_outer
+ .edges_filtered::(&asg)
+ .map(|oi| oi.resolve(&asg).span());
+
+ assert_eq!(vec![S3.merge(S4).unwrap()], inners.collect::>());
+
+ // Since the inner template is empty,
+ // so too should the outer.
+ let tpl_outer = oi_tpl_outer.resolve(&asg);
+ assert_eq!(TplShape::Empty, tpl_outer.shape());
+}
+
+// Template application should resolve all the same regardless of order of
+// ref/def.
+#[test]
+fn tpl_apply_nested_missing() {
+ let id_tpl_outer = spair("_tpl-outer_", S2);
+
+ let tpl_inner = "_tpl-inner_";
+ let id_tpl_inner = spair(tpl_inner, S7);
+ let ref_tpl_inner_pre = spair(tpl_inner, S4);
+ let ref_tpl_inner_post = spair(tpl_inner, S10);
+
+ #[rustfmt::skip]
+ let toks = [
+ TplStart(S1),
+ BindIdent(id_tpl_outer),
+
+ // Inner template application (Missing)
+ TplStart(S3),
+ RefIdent(ref_tpl_inner_pre),
+ TplEndRef(S5),
+
+ // Define the template above
+ TplStart(S6),
+ BindIdent(id_tpl_inner),
+ TplEnd(S8),
+
+ // Apply again,
+ // this time _after_ having been defined.
+ TplStart(S9),
+ RefIdent(ref_tpl_inner_post),
+ TplEndRef(S11),
+ TplEnd(S12),
+ ];
+
+ let ctx = air_ctx_from_pkg_body_toks(toks);
+ let asg = ctx.asg_ref();
+
+ let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
+ assert_eq!(S1.merge(S12).unwrap(), oi_tpl_outer.resolve(&asg).span());
+
+ // We apply two template,
+ // both of which are empty,
+ // and so the outer shape is still empty.
+ let tpl_outer = oi_tpl_outer.resolve(&asg);
+ assert_eq!(TplShape::Empty, tpl_outer.shape());
+
+ // The inner template should be contained within the outer and so not
+ // globally resolvable.
+ 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
+ .lookup_local_linear(&asg, id_tpl_inner)
+ .expect("could not locate inner template as a local")
+ .definition::(&asg)
+ .expect("could not resolve inner template ref to Tpl");
+
+ // We should have two inner template applications.
+ let inners = oi_tpl_outer.edges_filtered::(&asg).collect::>();
+ assert_eq!(
+ vec![S9.merge(S11).unwrap(), S3.merge(S5).unwrap()],
+ inners
+ .iter()
+ .map(|oi| oi.resolve(&asg).span())
+ .collect::>(),
+ );
+
+ // Each of those inner template applications should have resolved to the
+ // same template,
+ // despite their varying ref/def ordering.
+ assert_eq!(
+ vec![oi_tpl_inner, oi_tpl_inner],
+ inners
+ .iter()
+ .flat_map(|oi| oi.edges_filtered::(&asg))
+ .filter_map(|oi| oi.definition(&asg))
+ .collect::>(),
+ );
+}
+
+#[test]
+fn tpl_inner_apply_inherit_shape() {
+ let id_tpl_outer = spair("_tpl-outer_", S2);
+
+ #[rustfmt::skip]
+ let toks = [
+ TplStart(S1),
+ BindIdent(id_tpl_outer),
+
+ // Inner template application has an Expr shape.
+ TplStart(S3),
+ ExprStart(ExprOp::Sum, S4),
+ ExprEnd(S5),
+ TplEndRef(S6),
+ TplEnd(S7),
+ ];
+
+ let ctx = air_ctx_from_pkg_body_toks(toks);
+ let asg = ctx.asg_ref();
+
+ let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
+ let oi_tpl_inner = oi_tpl_outer.edges_filtered::(&asg).next().unwrap();
+
+ // The inner template's expression should determine its shape.
+ assert_eq!(
+ TplShape::Expr(S4.merge(S5).unwrap()),
+ oi_tpl_inner.resolve(&asg).shape()
+ );
+
+ // Since the outer template applies the inner,
+ // it inherits the inner's shape.
+ // (Imagine pasting the body of the inner template inline.)
+ assert_eq!(
+ TplShape::Expr(S3.merge(S6).unwrap()),
+ oi_tpl_outer.resolve(&asg).shape()
+ );
+}
+
+// It's okay to have sibling template applications that aren't Exprs.
+#[test]
+fn tpl_inner_apply_expr_alongside_empty() {
+ let id_tpl_outer = spair("_tpl-outer_", S2);
+
+ #[rustfmt::skip]
+ let toks = [
+ TplStart(S1),
+ BindIdent(id_tpl_outer),
+
+ // Inner template application has an Expr shape.
+ TplStart(S3),
+ ExprStart(ExprOp::Sum, S4),
+ ExprEnd(S5),
+ TplEndRef(S6),
+
+ // But this is empty.
+ TplStart(S7),
+ TplEndRef(S8),
+ TplEnd(S9),
+ ];
+
+ let ctx = air_ctx_from_pkg_body_toks(toks);
+ let asg = ctx.asg_ref();
+
+ let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
+
+ // The Expr shape takes precedence.
+ assert_eq!(
+ TplShape::Expr(S3.merge(S6).unwrap()),
+ oi_tpl_outer.resolve(&asg).shape()
+ );
+}
+
+// Template applications with Expr shapes yield the same errors as if the
+// bodies were just pasted inline,
+// with the exception of the spans that are reported.
+// The spans we provide to the user should be in the context of the template
+// being defined;
+// if we inherit the shape from the template,
+// then the span would be for the expression _inside_ that template,
+// which would compose to the innermost application's expression,
+// recursively.
+// And while that would certainly make for an impressive display if you
+// understood what was going on,
+// that breaks every layer of abstraction that was built and is not what
+// we want to provide.
+#[test]
+fn tpl_inner_apply_expr_alongside_another_apply_expr() {
+ let id_tpl_outer = spair("_tpl-outer_", S2);
+
+ #[rustfmt::skip]
+ let toks = [
+ TplStart(S1),
+ BindIdent(id_tpl_outer),
+
+ // Inner template application has an Expr shape.
+ TplStart(S3),
+ ExprStart(ExprOp::Sum, S4),
+ ExprEnd(S5),
+ TplEndRef(S6),
+
+ // As does this one,
+ // which should produce an error.
+ TplStart(S7),
+ ExprStart(ExprOp::Sum, S8),
+ ExprEnd(S9),
+ TplEndRef(S10),
+ TplEnd(S11),
+ ];
+
+ let mut sut = Sut::parse(as_pkg_body(toks));
+
+ assert_eq!(
+ #[rustfmt::skip]
+ vec![
+ Ok(Parsed::Incomplete), // PkgStart
+ Ok(Parsed::Incomplete), // TplStart
+ Ok(Parsed::Incomplete), // BindIdent
+
+ // This one's okay and changes the template's shape.
+ Ok(Parsed::Incomplete), // TplStart
+ Ok(Parsed::Incomplete), // ExprStart
+ Ok(Parsed::Incomplete), // ExprEnd
+ Ok(Parsed::Incomplete), // TplEnd
+
+ // But this one runs afoul of that new shape.
+ Ok(Parsed::Incomplete), // TplStart
+ Ok(Parsed::Incomplete), // ExprStart
+ Ok(Parsed::Incomplete), // ExprEnd
+ Err(ParseError::StateError(
+ // These spans are relative to the application itself.
+ // This is important to present the appropriate level of
+ // abstraction;
+ // see the comment for this test case.
+ AsgError::TplShapeExprMulti(
+ Some(id_tpl_outer),
+ S7.merge(S10).unwrap(),
+ S3.merge(S6).unwrap()
+ )
+ )),
+ // RECOVERY: We ignore the template by not adding the edge.
+ Ok(Parsed::Incomplete), // TplEnd >LA
+ Ok(Parsed::Incomplete), // TplEnd >(),
+ );
+
+ let ctx = sut.finalize().unwrap().into_private_context();
+ let asg = ctx.asg_ref();
+
+ let oi_tpl_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer);
+
+ assert_eq!(
+ TplShape::Expr(S3.merge(S6).unwrap()),
+ oi_tpl_outer.resolve(&asg).shape()
+ );
+
+ // The second template application should have been omitted.
+ assert_eq!(
+ vec![S3.merge(S6).unwrap()],
+ oi_tpl_outer
+ .edges_filtered::(&asg)
+ .map(ObjectIndex::cresolve(&asg))
+ .map(Tpl::span)
+ .collect::>()
+ );
+}