tamer: asg::air::tpl::test::apply: Move template application tests
This may not be the final home, but they're about to get much larger in number. DEV-13163main
parent
5dd7b7f1e8
commit
1c06605188
|
@ -17,6 +17,8 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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::<Tpl>(&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::<Expr>(&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::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_tpl_without_open() {
|
||||
let id_tpl = spair("_tpl_", S3);
|
||||
|
@ -534,27 +485,6 @@ fn unreachable_anonymous_tpl() {
|
|||
pkg_expect_ident_obj::<Tpl>(&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::<Tpl>(&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::<Tpl>(&asg)
|
||||
.map(|oi| oi.resolve(&asg).span());
|
||||
|
||||
assert_eq!(vec![S3.merge(S4).unwrap()], inners.collect::<Vec<_>>());
|
||||
|
||||
// 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::<Tpl>(&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::<Tpl>(&asg)
|
||||
.expect("could not resolve inner template ref to Tpl");
|
||||
|
||||
// We should have two inner template applications.
|
||||
let inners = oi_tpl_outer.edges_filtered::<Tpl>(&asg).collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
vec![S9.merge(S11).unwrap(), S3.merge(S5).unwrap()],
|
||||
inners
|
||||
.iter()
|
||||
.map(|oi| oi.resolve(&asg).span())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// 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::<Ident>(&asg))
|
||||
.filter_map(|oi| oi.definition(&asg))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[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::<Tpl>(&ctx, id_tpl_outer);
|
||||
let oi_tpl_inner = oi_tpl_outer.edges_filtered::<Tpl>(&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::<Tpl>(&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 <LA
|
||||
Ok(Parsed::Incomplete), // PkgEnd
|
||||
],
|
||||
sut.by_ref().collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let ctx = sut.finalize().unwrap().into_private_context();
|
||||
let asg = ctx.asg_ref();
|
||||
|
||||
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&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::<Tpl>(&asg)
|
||||
.map(ObjectIndex::cresolve(&asg))
|
||||
.map(Tpl::span)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tpl_doc_short_desc() {
|
||||
let id_tpl = spair("foo", S2);
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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::<Tpl>(&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::<Expr>(&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::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
// 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::<Tpl>(&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::<Tpl>(&asg)
|
||||
.map(|oi| oi.resolve(&asg).span());
|
||||
|
||||
assert_eq!(vec![S3.merge(S4).unwrap()], inners.collect::<Vec<_>>());
|
||||
|
||||
// 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::<Tpl>(&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::<Tpl>(&asg)
|
||||
.expect("could not resolve inner template ref to Tpl");
|
||||
|
||||
// We should have two inner template applications.
|
||||
let inners = oi_tpl_outer.edges_filtered::<Tpl>(&asg).collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
vec![S9.merge(S11).unwrap(), S3.merge(S5).unwrap()],
|
||||
inners
|
||||
.iter()
|
||||
.map(|oi| oi.resolve(&asg).span())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// 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::<Ident>(&asg))
|
||||
.filter_map(|oi| oi.definition(&asg))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[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::<Tpl>(&ctx, id_tpl_outer);
|
||||
let oi_tpl_inner = oi_tpl_outer.edges_filtered::<Tpl>(&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::<Tpl>(&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 <LA
|
||||
Ok(Parsed::Incomplete), // PkgEnd
|
||||
],
|
||||
sut.by_ref().collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let ctx = sut.finalize().unwrap().into_private_context();
|
||||
let asg = ctx.asg_ref();
|
||||
|
||||
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&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::<Tpl>(&asg)
|
||||
.map(ObjectIndex::cresolve(&asg))
|
||||
.map(Tpl::span)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue