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
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
mod apply;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::asg::air::test::{as_pkg_body, Sut};
|
use crate::asg::air::test::{as_pkg_body, Sut};
|
||||||
use crate::span::dummy::*;
|
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]
|
#[test]
|
||||||
fn close_tpl_without_open() {
|
fn close_tpl_without_open() {
|
||||||
let id_tpl = spair("_tpl_", S3);
|
let id_tpl = spair("_tpl_", S3);
|
||||||
|
@ -534,27 +485,6 @@ fn unreachable_anonymous_tpl() {
|
||||||
pkg_expect_ident_obj::<Tpl>(&ctx, id_ok);
|
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]
|
#[test]
|
||||||
fn tpl_with_param() {
|
fn tpl_with_param() {
|
||||||
let id_tpl = spair("_tpl_", S2);
|
let id_tpl = spair("_tpl_", S2);
|
||||||
|
@ -678,298 +608,6 @@ fn tpl_nested() {
|
||||||
assert_eq!(TplShape::Empty, tpl_outer.shape());
|
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]
|
#[test]
|
||||||
fn tpl_doc_short_desc() {
|
fn tpl_doc_short_desc() {
|
||||||
let id_tpl = spair("foo", S2);
|
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