tamer: asg::graph::object::tpl: Infer shape from referenced identifiers
Oh, identifiers. What a mess you make of the graph. As indirection tends to do, I suppose. This still needs some cleanup; I wanted to get something working first. In particular: - Common operations need refactoring; - Having all this code in the edge definition makes for a mess; - `Ident` references really ought not be returned by `Ident::definition` since an identifier will never be a definition. ...I'm sure there's more. DEV-13163main
parent
087ef45153
commit
deede5ff21
|
@ -149,18 +149,18 @@ fn tpl_apply_nested_missing() {
|
||||||
|
|
||||||
// Inner template application (Missing)
|
// Inner template application (Missing)
|
||||||
TplStart(S3),
|
TplStart(S3),
|
||||||
RefIdent(ref_tpl_inner_pre),
|
RefIdent(ref_tpl_inner_pre), // -.
|
||||||
TplEndRef(S5),
|
TplEndRef(S5), // |
|
||||||
|
// |
|
||||||
// Define the template above
|
// Define the template above // |
|
||||||
TplStart(S6),
|
TplStart(S6), // |
|
||||||
BindIdent(id_tpl_inner),
|
BindIdent(id_tpl_inner), // <:
|
||||||
TplEnd(S8),
|
TplEnd(S8), // |
|
||||||
|
// |
|
||||||
// Apply again,
|
// Apply again, // |
|
||||||
// this time _after_ having been defined.
|
// this time _after_ having been defined. // |
|
||||||
TplStart(S9),
|
TplStart(S9), // |
|
||||||
RefIdent(ref_tpl_inner_post),
|
RefIdent(ref_tpl_inner_post), // -'
|
||||||
TplEndRef(S11),
|
TplEndRef(S11),
|
||||||
TplEnd(S12),
|
TplEnd(S12),
|
||||||
];
|
];
|
||||||
|
@ -171,11 +171,17 @@ fn tpl_apply_nested_missing() {
|
||||||
let oi_tpl_outer = pkg_expect_ident_oi::<Tpl>(&ctx, 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());
|
assert_eq!(S1.merge(S12).unwrap(), oi_tpl_outer.resolve(&asg).span());
|
||||||
|
|
||||||
// We apply two template,
|
// TODO: It should be the case that we apply two templates,
|
||||||
// both of which are empty,
|
// both of which are empty,
|
||||||
// and so the outer shape is still empty.
|
// and so the outer shape is still empty.
|
||||||
|
// But until we notify templates of `Missing` ident resolution,
|
||||||
|
// we don't know the shape of the template by the time we reach the
|
||||||
|
// first reference.
|
||||||
|
// Consequently,
|
||||||
|
// `TplShape::Unknown` must take precedence to reflect this
|
||||||
|
// uncertainty.
|
||||||
let tpl_outer = oi_tpl_outer.resolve(&asg);
|
let tpl_outer = oi_tpl_outer.resolve(&asg);
|
||||||
assert_eq!(TplShape::Empty, tpl_outer.shape());
|
assert_eq!(TplShape::Unknown(S3.merge(S5).unwrap()), tpl_outer.shape());
|
||||||
|
|
||||||
// The inner template should be contained within the outer and so not
|
// The inner template should be contained within the outer and so not
|
||||||
// globally resolvable.
|
// globally resolvable.
|
||||||
|
@ -378,3 +384,146 @@ fn tpl_inner_apply_expr_alongside_another_apply_expr() {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A simpler version of the above test that asserts against the explicit
|
||||||
|
// case of sibling expressions in a template body,
|
||||||
|
// with no wrapping or indirection.
|
||||||
|
#[test]
|
||||||
|
fn tpl_multi_expr() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = [
|
||||||
|
TplStart(S1),
|
||||||
|
BindIdent(spair("_tpl_", S2)),
|
||||||
|
|
||||||
|
// First one is okay.
|
||||||
|
ExprStart(ExprOp::Sum, S3),
|
||||||
|
ExprEnd(S4),
|
||||||
|
|
||||||
|
// But the second is in error and will be dropped.
|
||||||
|
ExprStart(ExprOp::Sum, S5),
|
||||||
|
ExprEnd(S6),
|
||||||
|
TplEnd(S7),
|
||||||
|
];
|
||||||
|
|
||||||
|
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), // ExprStart
|
||||||
|
Ok(Parsed::Incomplete), // ExprEnd
|
||||||
|
|
||||||
|
// But this one runs afoul of that new shape.
|
||||||
|
Ok(Parsed::Incomplete), // ExprStart
|
||||||
|
Err(ParseError::StateError(
|
||||||
|
AsgError::TplShapeExprMulti(
|
||||||
|
Some(spair("_tpl_", S2)),
|
||||||
|
S5.merge(S6).unwrap(),
|
||||||
|
S3.merge(S4).unwrap(),
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
// RECOVERY: We ignore the expression by not adding its
|
||||||
|
// edge.
|
||||||
|
Ok(Parsed::Incomplete), // TplEnd
|
||||||
|
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 = pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl_", S8));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TplShape::Expr(S3.merge(S4).unwrap()),
|
||||||
|
oi_tpl.resolve(&asg).shape()
|
||||||
|
);
|
||||||
|
|
||||||
|
// The second Expr should have been omitted.
|
||||||
|
assert_eq!(
|
||||||
|
vec![S3.merge(S4).unwrap()],
|
||||||
|
oi_tpl
|
||||||
|
.edges_filtered::<Expr>(&asg)
|
||||||
|
.map(ObjectIndex::cresolve(&asg))
|
||||||
|
.map(Expr::span)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifiers are a layer of indirection,
|
||||||
|
// but the object that they identify should influence the shape of the
|
||||||
|
// template all the same.
|
||||||
|
#[test]
|
||||||
|
fn tpl_ref_expr_shape() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = [
|
||||||
|
TplStart(S1),
|
||||||
|
BindIdent(spair("_tpl-pre_", S2)),
|
||||||
|
|
||||||
|
RefIdent(spair("expr", S3)), // --,
|
||||||
|
TplEnd(S4), // |
|
||||||
|
// | U
|
||||||
|
ExprStart(ExprOp::Sum, S5), // |
|
||||||
|
BindIdent(spair("expr", S6)), // <=:
|
||||||
|
ExprEnd(S7), // |
|
||||||
|
// | E
|
||||||
|
TplStart(S8), // |
|
||||||
|
BindIdent(spair("_tpl-post_", S9)), // <-+-.
|
||||||
|
// | |
|
||||||
|
RefIdent(spair("expr", S10)), // --' |
|
||||||
|
TplEnd(S11), // |
|
||||||
|
// |
|
||||||
|
// Similarly, // |
|
||||||
|
// if we have a template that references // |
|
||||||
|
// one of the above templates, // |
|
||||||
|
// it too should have the same // | E
|
||||||
|
// Expr shape, // |
|
||||||
|
// since the reference represents // |
|
||||||
|
// application. // |
|
||||||
|
TplStart(S12), // |
|
||||||
|
BindIdent(spair("_tpl-post-tpl-ref_", S13)),// |
|
||||||
|
// |
|
||||||
|
RefIdent(spair("_tpl-post_", S14)), // ----'
|
||||||
|
TplEnd(S14),
|
||||||
|
];
|
||||||
|
|
||||||
|
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||||
|
let asg = ctx.asg_ref();
|
||||||
|
|
||||||
|
let oi_tpl_pre = pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl-pre_", S15));
|
||||||
|
let oi_tpl_post =
|
||||||
|
pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl-post_", S16));
|
||||||
|
let oi_tpl_post_tpl_ref =
|
||||||
|
pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl-post-tpl-ref_", S17));
|
||||||
|
|
||||||
|
// TODO: We haven't yet handled notification when an identifier has been
|
||||||
|
// resolved,
|
||||||
|
// so the shape will be unknown until then.
|
||||||
|
assert_eq!(TplShape::Unknown(S3), oi_tpl_pre.resolve(&asg).shape(),);
|
||||||
|
|
||||||
|
// But the template defined _after_ the expression will be immediately
|
||||||
|
// resolved and its shape known.
|
||||||
|
assert_eq!(
|
||||||
|
// Note that the span is that of the _reference_,
|
||||||
|
// since that is what appears within the template body and that
|
||||||
|
// which we want to emphasize in diagnostics.
|
||||||
|
TplShape::Expr(S10),
|
||||||
|
oi_tpl_post.resolve(&asg).shape(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// And the final template applies the preceding one,
|
||||||
|
// and should inherit its shape since.
|
||||||
|
assert_eq!(
|
||||||
|
// Just as above,
|
||||||
|
// the span is the topmost _reference_,
|
||||||
|
// which is the application.
|
||||||
|
TplShape::Expr(S14),
|
||||||
|
oi_tpl_post_tpl_ref.resolve(&asg).shape(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -533,7 +533,6 @@ impl<OA: ObjectRelatable, OB: ObjectRelatable> AsgRelMut<OB> for OA {
|
||||||
_rel: ProposedRel<Self, OB>,
|
_rel: ProposedRel<Self, OB>,
|
||||||
commit: impl FnOnce(&mut Asg),
|
commit: impl FnOnce(&mut Asg),
|
||||||
) -> Result<(), AsgError> {
|
) -> Result<(), AsgError> {
|
||||||
let _ = _rel.ctx_span; // TODO: remove when used (dead_code)
|
|
||||||
Ok(commit(asg))
|
Ok(commit(asg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1291,6 +1291,8 @@ impl ObjectIndex<Ident> {
|
||||||
&self,
|
&self,
|
||||||
asg: &Asg,
|
asg: &Asg,
|
||||||
) -> Option<<Ident as ObjectRelatable>::Rel> {
|
) -> Option<<Ident as ObjectRelatable>::Rel> {
|
||||||
|
// XXX: This could return an abstract binding metavar reference
|
||||||
|
// depending on undefined edge ordering!
|
||||||
self.edges(asg).next()
|
self.edges(asg).next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,13 @@
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::{prelude::*, Doc, Expr, Ident};
|
use super::{ident::IdentRel, prelude::*, Doc, Expr, Ident};
|
||||||
use crate::{asg::graph::ProposedRel, f::Map, parse::util::SPair, span::Span};
|
use crate::{
|
||||||
|
asg::graph::ProposedRel,
|
||||||
|
f::Map,
|
||||||
|
parse::{prelude::Annotate, util::SPair},
|
||||||
|
span::Span,
|
||||||
|
};
|
||||||
|
|
||||||
/// Template with associated name.
|
/// Template with associated name.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -123,7 +128,10 @@ pub enum TplShape {
|
||||||
/// completed.
|
/// completed.
|
||||||
/// Note that a definition is not complete until all missing identifiers
|
/// Note that a definition is not complete until all missing identifiers
|
||||||
/// have been defined.
|
/// have been defined.
|
||||||
Unknown,
|
///
|
||||||
|
/// The associated span represents the location that resulted in
|
||||||
|
/// uncertainty.
|
||||||
|
Unknown(Span),
|
||||||
|
|
||||||
/// The template can be expanded inline into a single [`Expr`].
|
/// The template can be expanded inline into a single [`Expr`].
|
||||||
///
|
///
|
||||||
|
@ -158,18 +166,35 @@ impl TplShape {
|
||||||
)),
|
)),
|
||||||
|
|
||||||
// Higher levels of specificity take precedence.
|
// Higher levels of specificity take precedence.
|
||||||
(shape @ TplShape::Expr(_), TplShape::Empty)
|
// This pattern is designed to be very clear in what shape takes
|
||||||
| (TplShape::Empty, shape @ TplShape::Expr(_))
|
// precedence over another.
|
||||||
| (shape @ TplShape::Empty, TplShape::Empty) => Ok(shape),
|
// It should be clear enough that there is no value in writing
|
||||||
|
// unit test against this method since those tests' examples
|
||||||
// Unknown is not yet handled.
|
// would simply reiterate this table
|
||||||
|
// (but tests for AIR should still be written to test more
|
||||||
|
// complex interactions).
|
||||||
|
#[rustfmt::skip]
|
||||||
(
|
(
|
||||||
TplShape::Unknown,
|
TplShape::Empty,
|
||||||
TplShape::Empty | TplShape::Unknown | TplShape::Expr(_),
|
give_precedence_to @ (
|
||||||
|
TplShape::Empty
|
||||||
|
| TplShape::Unknown(_)
|
||||||
|
| TplShape::Expr(_)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
| (TplShape::Empty | TplShape::Expr(_), TplShape::Unknown) => {
|
| (
|
||||||
todo!("TplShape::Unknown")
|
TplShape::Unknown(_),
|
||||||
}
|
give_precedence_to @ TplShape::Expr(_),
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
give_precedence_to @ TplShape::Unknown(_),
|
||||||
|
TplShape::Empty | TplShape::Unknown(_),
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
give_precedence_to @ TplShape::Expr(_),
|
||||||
|
TplShape::Empty | TplShape::Unknown(_),
|
||||||
|
)
|
||||||
|
=> Ok(give_precedence_to),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +208,8 @@ impl TplShape {
|
||||||
/// its own body.
|
/// its own body.
|
||||||
fn overwrite_span_if_any(self, span: Span) -> Self {
|
fn overwrite_span_if_any(self, span: Span) -> Self {
|
||||||
match self {
|
match self {
|
||||||
TplShape::Empty | TplShape::Unknown => self,
|
TplShape::Empty => self,
|
||||||
|
TplShape::Unknown(_) => TplShape::Unknown(span),
|
||||||
TplShape::Expr(_) => TplShape::Expr(span),
|
TplShape::Expr(_) => TplShape::Expr(span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,8 +230,8 @@ impl Display for TplShape {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
// phrase as "template with ..."
|
// phrase as "template with ..."
|
||||||
match self {
|
match self {
|
||||||
TplShape::Unknown => write!(f, "unknown shape"),
|
|
||||||
TplShape::Empty => write!(f, "empty shape"),
|
TplShape::Empty => write!(f, "empty shape"),
|
||||||
|
TplShape::Unknown(_) => write!(f, "unknown shape"),
|
||||||
TplShape::Expr(_) => write!(f, "shape of a single expression"),
|
TplShape::Expr(_) => write!(f, "shape of a single expression"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,7 +263,78 @@ object_rel! {
|
||||||
|
|
||||||
// Identifiers are used for both references and identifiers that
|
// Identifiers are used for both references and identifiers that
|
||||||
// will expand into an application site.
|
// will expand into an application site.
|
||||||
dyn Ident,
|
dyn Ident {
|
||||||
|
fn pre_add_edge(
|
||||||
|
asg: &mut Asg,
|
||||||
|
rel: ProposedRel<Self, Ident>,
|
||||||
|
commit: impl FnOnce(&mut Asg),
|
||||||
|
) -> Result<(), AsgError> {
|
||||||
|
let tpl_name = rel.from_oi.name(asg);
|
||||||
|
|
||||||
|
match (rel.ctx_span, rel.to_oi.definition(asg)) {
|
||||||
|
// Missing definition results in shape uncertainty that
|
||||||
|
// will have to be resolved when (if) a definition
|
||||||
|
// becomes available.
|
||||||
|
(Some(ref_span), None) => {
|
||||||
|
rel.from_oi.try_map_obj_inner(
|
||||||
|
asg,
|
||||||
|
try_adapt_to(TplShape::Unknown(ref_span), tpl_name),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TAME is referentally transparent,
|
||||||
|
// so a reference to an Expr is no different than
|
||||||
|
// inlining that Expr.
|
||||||
|
(Some(ref_span), Some(IdentRel::Expr(_))) => {
|
||||||
|
rel.from_oi.try_map_obj_inner(
|
||||||
|
asg,
|
||||||
|
try_adapt_to(TplShape::Expr(ref_span), tpl_name),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the same as the `Tpl` tree edge below,
|
||||||
|
// but a named template instead of an anonymous one.
|
||||||
|
(Some(ref_span), Some(IdentRel::Tpl(to_oi))) => {
|
||||||
|
// TODO: Factor common logic between this and the
|
||||||
|
// `Tpl->Tpl` edge below.
|
||||||
|
let tpl_name = to_oi.name(asg);
|
||||||
|
let apply = to_oi.resolve(asg);
|
||||||
|
let apply_shape = apply
|
||||||
|
.shape()
|
||||||
|
.overwrite_span_if_any(ref_span);
|
||||||
|
|
||||||
|
rel.from_oi.try_map_obj_inner(
|
||||||
|
asg,
|
||||||
|
try_adapt_to(apply_shape, tpl_name),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Filter this out (Ident -> Ident)
|
||||||
|
(Some(span), Some(IdentRel::Ident(_))) => {
|
||||||
|
diagnostic_todo!(
|
||||||
|
vec![span.internal_error("while parsing this reference")],
|
||||||
|
"opaque identifier or abstract binding"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mere _existence_ of metavariables (template
|
||||||
|
// params) do not influence the expansion shape.
|
||||||
|
(Some(_), Some(IdentRel::Meta(_))) => (),
|
||||||
|
|
||||||
|
// Lack of span means that this is not a cross edge,
|
||||||
|
// and so not a reference;
|
||||||
|
// this means that the object is identified and will
|
||||||
|
// be hoisted into the rooting context of the
|
||||||
|
// application site,
|
||||||
|
// which does not impact template shape.
|
||||||
|
// TODO: Let's make that span assumption explicit in the
|
||||||
|
// `ProposeRel` abstraction.
|
||||||
|
(None, _) => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(commit(asg))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Template application.
|
// Template application.
|
||||||
tree Tpl {
|
tree Tpl {
|
||||||
|
|
Loading…
Reference in New Issue