tamer: asg::air: Support abstract biding of `Expr`s
This produces a representation of abstract identifiers on the graph, for `Expr`s at least. The next step will probably be to get this working end-to-end in the xmli output before extending it to the other remaining bindable contexts. DEV-13163main
parent
a144730981
commit
24ee041373
|
@ -138,17 +138,54 @@ impl ParseState for AirExprAggregate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(BuildingExpr(_, oi), AirBind(BindIdentAbstract(meta_name))) => {
|
(BuildingExpr(es, oi), AirBind(BindIdentAbstract(meta_name))) => {
|
||||||
diagnostic_todo!(
|
// To help mitigate potentially cryptic errors down the line,
|
||||||
vec![
|
// let's try to be helpful and notify the user when
|
||||||
oi.note("for this expression"),
|
// they're trying to do something that almost certainly
|
||||||
meta_name.note(
|
// will not succeed.
|
||||||
"attempting to bind an abstract identifier with \
|
match ctx.dangling_expr_oi() {
|
||||||
this metavariable"
|
// The container does not support dangling expressions
|
||||||
),
|
// and so there is no chance that this expression will
|
||||||
],
|
// be expanded in the future.
|
||||||
"attempt to bind abstract identifier to expression",
|
None => {
|
||||||
)
|
let rooting_span = ctx
|
||||||
|
.rooting_oi()
|
||||||
|
.map(|oi| oi.widen().resolve(ctx.asg_ref()).span());
|
||||||
|
|
||||||
|
// Note that we _discard_ the attempted bind token
|
||||||
|
// and so remain in a dangling state.
|
||||||
|
Transition(BuildingExpr(es, oi)).err(
|
||||||
|
AsgError::InvalidAbstractBindContext(
|
||||||
|
meta_name,
|
||||||
|
rooting_span,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't care what our container is,
|
||||||
|
// only that the above check passed.
|
||||||
|
// That is:
|
||||||
|
// the above check is entirely optional and intended
|
||||||
|
// only as a debugging aid for users.
|
||||||
|
Some(_) => {
|
||||||
|
let oi_meta_ident =
|
||||||
|
ctx.lookup_lexical_or_missing(meta_name);
|
||||||
|
|
||||||
|
let oi_abstract = oi_meta_ident.new_abstract_ident(
|
||||||
|
ctx.asg_mut(),
|
||||||
|
meta_name.span(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Note that we are still dangling,
|
||||||
|
// since the identifier is abstract and is
|
||||||
|
// therefore not yet reachable via its
|
||||||
|
// yet-to-be-determined identifier.
|
||||||
|
oi_abstract
|
||||||
|
.bind_definition(ctx.asg_mut(), meta_name, oi)
|
||||||
|
.map(|_| ())
|
||||||
|
.transition(BuildingExpr(es, oi))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(BuildingExpr(es, oi), AirBind(RefIdent(name))) => {
|
(BuildingExpr(es, oi), AirBind(RefIdent(name))) => {
|
||||||
|
|
|
@ -813,3 +813,73 @@ fn expr_doc_short_desc() {
|
||||||
oi_docs.collect::<Vec<_>>(),
|
oi_docs.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Binding an abstract identifier to an expression means that the expression
|
||||||
|
// may _eventually_ be reachable after expansion,
|
||||||
|
// but it is not yet.
|
||||||
|
// They must therefore only be utilized within the context of a container
|
||||||
|
// that supports dangling expressions,
|
||||||
|
// like a template.
|
||||||
|
#[test]
|
||||||
|
fn abstract_bind_without_dangling_container() {
|
||||||
|
let id_meta = SPair("@foo@".into(), S2);
|
||||||
|
let id_ok = SPair("concrete".into(), S5);
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = vec![
|
||||||
|
Air::ExprStart(ExprOp::Sum, S1),
|
||||||
|
// This expression is bound to an _abstract_ identifier,
|
||||||
|
// which will be expanded at a later time.
|
||||||
|
// Consequently,
|
||||||
|
// this expression is still dangling.
|
||||||
|
Air::BindIdentAbstract(id_meta),
|
||||||
|
|
||||||
|
// Since the expression is still dangling,
|
||||||
|
// attempting to close it will produce an error.
|
||||||
|
Air::ExprEnd(S3),
|
||||||
|
|
||||||
|
// RECOVERY: Since an attempt at identification has been made,
|
||||||
|
// we shouldn't expect that another attempt will be made.
|
||||||
|
// The sensible thing to do is to move on to try to find other
|
||||||
|
// errors,
|
||||||
|
// leaving the expression alone and unreachable.
|
||||||
|
Air::ExprStart(ExprOp::Sum, S4),
|
||||||
|
// This is intended to demonstrate that we can continue on to the
|
||||||
|
// next expression despite the prior error.
|
||||||
|
Air::BindIdent(id_ok),
|
||||||
|
Air::ExprEnd(S6),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut sut = parse_as_pkg_body(toks);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
#[rustfmt::skip]
|
||||||
|
vec![
|
||||||
|
Ok(Parsed::Incomplete), // PkgStart
|
||||||
|
Ok(Parsed::Incomplete), // ExprStart
|
||||||
|
|
||||||
|
// This provides an _abstract_ identifier,
|
||||||
|
// which is not permitted in this context.
|
||||||
|
Err(ParseError::StateError(AsgError::InvalidAbstractBindContext(
|
||||||
|
id_meta,
|
||||||
|
Some(S1), // Pkg
|
||||||
|
))),
|
||||||
|
|
||||||
|
// RECOVERY: Ignore the bind and move to close.
|
||||||
|
// The above identifier was rejected and so we are still dangling.
|
||||||
|
Err(ParseError::StateError(AsgError::DanglingExpr(
|
||||||
|
S1.merge(S3).unwrap()
|
||||||
|
))),
|
||||||
|
|
||||||
|
// RECOVERY: This observes that we're able to continue parsing
|
||||||
|
// the package after the above identification problem.
|
||||||
|
Ok(Parsed::Incomplete), // ExprStart
|
||||||
|
Ok(Parsed::Incomplete), // BindIdent (ok)
|
||||||
|
Ok(Parsed::Incomplete), // ExprEnd
|
||||||
|
Ok(Parsed::Incomplete), // PkgEnd
|
||||||
|
],
|
||||||
|
sut.by_ref().collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = sut.finalize().unwrap();
|
||||||
|
}
|
||||||
|
|
|
@ -785,3 +785,52 @@ fn metavars_within_exprs_hoisted_to_parent_tpl() {
|
||||||
|
|
||||||
assert_eq!(S11.merge(S13).unwrap(), span_inner);
|
assert_eq!(S11.merge(S13).unwrap(), span_inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expr_abstract_bind_produces_cross_edge_from_ident_to_meta() {
|
||||||
|
let id_tpl = SPair("_tpl_".into(), S2);
|
||||||
|
let id_meta = SPair("@foo@".into(), S4);
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let toks = vec![
|
||||||
|
Air::TplStart(S1),
|
||||||
|
// This identifier is concrete;
|
||||||
|
// the abstract identifier will be the _expression_.
|
||||||
|
Air::BindIdent(id_tpl),
|
||||||
|
|
||||||
|
Air::ExprStart(ExprOp::Sum, S3),
|
||||||
|
// This expression is bound to an _abstract_ identifier,
|
||||||
|
// which will be expanded at a later time.
|
||||||
|
// This does _not_ change the dangling status,
|
||||||
|
// and so can only occur within an expression that acts as a
|
||||||
|
// container for otherwise-dangling expressions.
|
||||||
|
Air::BindIdentAbstract(id_meta),
|
||||||
|
Air::ExprEnd(S5),
|
||||||
|
Air::TplEnd(S6),
|
||||||
|
];
|
||||||
|
|
||||||
|
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||||
|
let asg = ctx.asg_ref();
|
||||||
|
|
||||||
|
// The expression is dangling and so we must find it through a
|
||||||
|
// traversal.
|
||||||
|
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, id_tpl);
|
||||||
|
let oi_expr = oi_tpl
|
||||||
|
.edges_filtered::<Expr>(asg)
|
||||||
|
.next()
|
||||||
|
.expect("could not locate child Expr");
|
||||||
|
|
||||||
|
// The abstract identifier that should have been bound to the expression.
|
||||||
|
let oi_ident = oi_expr
|
||||||
|
.ident(asg)
|
||||||
|
.expect("abstract identifier was not bound to Expr");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
None,
|
||||||
|
oi_ident.resolve(asg).name(),
|
||||||
|
"abstract identifier must not have a concrete name",
|
||||||
|
);
|
||||||
|
|
||||||
|
// The metavariable referenced by the abstract identifier
|
||||||
|
assert_eq!(id_meta, oi_ident.name_or_meta(asg));
|
||||||
|
}
|
||||||
|
|
|
@ -117,13 +117,20 @@ pub enum AsgError {
|
||||||
/// delimiter.
|
/// delimiter.
|
||||||
UnbalancedTpl(Span),
|
UnbalancedTpl(Span),
|
||||||
|
|
||||||
/// Attempted to bind an identifier to an object while not in a context
|
/// Attempted to bind a concrete identifier to an object while not in a
|
||||||
/// that can receive an identifier binding.
|
/// context that can receive an identifier binding.
|
||||||
///
|
///
|
||||||
/// Note that the user may encounter an error from a higher-level IR
|
/// Note that the user may encounter an error from a higher-level IR
|
||||||
/// instead of this one.
|
/// instead of this one.
|
||||||
InvalidBindContext(SPair),
|
InvalidBindContext(SPair),
|
||||||
|
|
||||||
|
/// Attempted to bind an abstract identifier in a context where
|
||||||
|
/// expansion will not take place.
|
||||||
|
///
|
||||||
|
/// This is intended to preempt future errors when we know that the
|
||||||
|
/// context does not make sense for an abstract binding.
|
||||||
|
InvalidAbstractBindContext(SPair, Option<Span>),
|
||||||
|
|
||||||
/// Attempted to reference an identifier while not in a context that can
|
/// Attempted to reference an identifier while not in a context that can
|
||||||
/// receive an identifier reference.
|
/// receive an identifier reference.
|
||||||
///
|
///
|
||||||
|
@ -202,8 +209,20 @@ impl Display for AsgError {
|
||||||
),
|
),
|
||||||
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
|
UnbalancedExpr(_) => write!(f, "unbalanced expression"),
|
||||||
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
|
UnbalancedTpl(_) => write!(f, "unbalanced template definition"),
|
||||||
InvalidBindContext(_) => {
|
InvalidBindContext(name) => {
|
||||||
write!(f, "invalid identifier binding context")
|
write!(
|
||||||
|
f,
|
||||||
|
"invalid identifier binding context for {}",
|
||||||
|
TtQuote::wrap(name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
InvalidAbstractBindContext(name, _) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"invalid abstract identifier binding context for \
|
||||||
|
metavariable {}",
|
||||||
|
TtQuote::wrap(name),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
InvalidRefContext(ident) => {
|
InvalidRefContext(ident) => {
|
||||||
write!(
|
write!(
|
||||||
|
@ -351,6 +370,25 @@ impl Diagnostic for AsgError {
|
||||||
InvalidBindContext(name) => vec![name
|
InvalidBindContext(name) => vec![name
|
||||||
.error("an identifier binding is not valid in this context")],
|
.error("an identifier binding is not valid in this context")],
|
||||||
|
|
||||||
|
InvalidAbstractBindContext(name, parent_span) => parent_span
|
||||||
|
.map(|span| {
|
||||||
|
span.note(
|
||||||
|
"this definition context does not support metavariable \
|
||||||
|
expansion"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.chain(vec![
|
||||||
|
name.error("this metavariable will never be expanded"),
|
||||||
|
name.help(format!(
|
||||||
|
"this identifier must have its name derived from \
|
||||||
|
the metavariable {},
|
||||||
|
but that metavariable will never be expanded here",
|
||||||
|
TtQuote::wrap(name),
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
.collect(),
|
||||||
|
|
||||||
InvalidRefContext(ident) => vec![ident.error(
|
InvalidRefContext(ident) => vec![ident.error(
|
||||||
"cannot reference the value of an expression from outside \
|
"cannot reference the value of an expression from outside \
|
||||||
of an expression context",
|
of an expression context",
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
use super::{prelude::*, Expr, Meta, Pkg, Tpl};
|
use super::{prelude::*, Expr, Meta, Pkg, Tpl};
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnose::{Annotate, Diagnostic},
|
diagnose::{panic::DiagnosticPanic, Annotate, Diagnostic},
|
||||||
diagnostic_todo,
|
diagnostic_todo,
|
||||||
f::Functor,
|
f::Functor,
|
||||||
fmt::{DisplayWrapper, TtQuote},
|
fmt::{DisplayWrapper, TtQuote},
|
||||||
|
@ -271,6 +271,17 @@ impl Ident {
|
||||||
Missing(ident)
|
Missing(ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new abstract identifier at the given location.
|
||||||
|
///
|
||||||
|
/// The provided [`Span`] is the only way to uniquely identify this
|
||||||
|
/// identifier since it does not yet have a name.
|
||||||
|
/// Note that this just _represents_ an abstract identifier;
|
||||||
|
/// it is given meaning only when given the proper relationships on
|
||||||
|
/// the ASG.
|
||||||
|
pub fn new_abstract<S: Into<Span>>(at: S) -> Self {
|
||||||
|
Abstract(at.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to redeclare an identifier with additional information.
|
/// Attempt to redeclare an identifier with additional information.
|
||||||
///
|
///
|
||||||
/// If an existing identifier is an [`Ident::Extern`],
|
/// If an existing identifier is an [`Ident::Extern`],
|
||||||
|
@ -1106,7 +1117,10 @@ object_rel! {
|
||||||
/// This is a legacy feature expected to be removed in the future;
|
/// This is a legacy feature expected to be removed in the future;
|
||||||
/// see [`ObjectRel::can_recurse`] for more information.
|
/// see [`ObjectRel::can_recurse`] for more information.
|
||||||
Ident -> {
|
Ident -> {
|
||||||
tree Ident,
|
// Could represent an opaque dependency or an abstract identifier's
|
||||||
|
// metavariable reference.
|
||||||
|
dyn Ident,
|
||||||
|
|
||||||
tree Expr,
|
tree Expr,
|
||||||
tree Tpl,
|
tree Tpl,
|
||||||
tree Meta,
|
tree Meta,
|
||||||
|
@ -1224,11 +1238,22 @@ impl ObjectIndex<Ident> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g. in a template body
|
// An abstract identifier will become `Transparent` during
|
||||||
Abstract(span) => diagnostic_todo!(
|
// expansion.
|
||||||
vec![span.note("abstract defintion bind here")],
|
// This does not catch multiple definitions,
|
||||||
"bind definition to abstract identifier",
|
// but this is hopefully not a problem in practice since there
|
||||||
),
|
// is no lookup mechanism in source languages for abstract
|
||||||
|
// identifiers since this has no name yet and cannot be
|
||||||
|
// indexed in the usual way.
|
||||||
|
// Even so,
|
||||||
|
// multiple definitions can be caught at expansion-time if
|
||||||
|
// they somehow are able to slip through
|
||||||
|
// (which would be a compiler bug);
|
||||||
|
// it is not worth complicating `Ident`'s API or variants
|
||||||
|
// even further,
|
||||||
|
// and not worth the cost of a graph lookup here when
|
||||||
|
// we'll have to do it later anyway.
|
||||||
|
Abstract(span) => Ok(Abstract(span)),
|
||||||
|
|
||||||
// We are okay to proceed to add an edge to the `definition`.
|
// We are okay to proceed to add an edge to the `definition`.
|
||||||
// Discard the original span
|
// Discard the original span
|
||||||
|
@ -1305,13 +1330,66 @@ impl ObjectIndex<Ident> {
|
||||||
pub fn name_or_meta(&self, asg: &Asg) -> SPair {
|
pub fn name_or_meta(&self, asg: &Asg) -> SPair {
|
||||||
let ident = self.resolve(asg);
|
let ident = self.resolve(asg);
|
||||||
|
|
||||||
|
// It would be nice if this could be built more into the type system
|
||||||
|
// in the future,
|
||||||
|
// if it's worth the effort of doing so.
|
||||||
|
// This is a simple lookup;
|
||||||
|
// the robust internal diagnostic messages make it look
|
||||||
|
// more complicated than it is.
|
||||||
ident.name().unwrap_or_else(|| {
|
ident.name().unwrap_or_else(|| {
|
||||||
diagnostic_todo!(
|
let oi_meta_ident =
|
||||||
vec![ident.span().internal_error("for this abstract ident")],
|
self.edges_filtered::<Ident>(asg).next().diagnostic_expect(
|
||||||
"metavariable lookup not yet supported",
|
|| {
|
||||||
|
vec![
|
||||||
|
self.internal_error(
|
||||||
|
"this abstract identifier has no Ident edge",
|
||||||
|
),
|
||||||
|
self.help(
|
||||||
|
"the compiler created an `Ident::Abstract` \
|
||||||
|
object but did not produce an edge to the \
|
||||||
|
Ident of the Meta from which its name \
|
||||||
|
will be derived",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"invalid ASG representation of abstract identifier",
|
||||||
|
);
|
||||||
|
|
||||||
|
oi_meta_ident.resolve(asg).name().diagnostic_expect(
|
||||||
|
|| {
|
||||||
|
vec![
|
||||||
|
self.note(
|
||||||
|
"while trying to find the Meta name of this abstract \
|
||||||
|
identifier"
|
||||||
|
),
|
||||||
|
oi_meta_ident.internal_error(
|
||||||
|
"encountered another abstract identifier"
|
||||||
|
),
|
||||||
|
oi_meta_ident.help(
|
||||||
|
"an abstract identifier must reference a concrete \
|
||||||
|
`Ident`"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"abstract identifier references another abstract identifier",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new abstract identifier whose name will be derived from
|
||||||
|
/// this one during expansion.
|
||||||
|
///
|
||||||
|
/// It is expected that `self` defines a [`Meta`],
|
||||||
|
/// but this is not enforced here and will be checked during
|
||||||
|
/// expansion.
|
||||||
|
pub fn new_abstract_ident(
|
||||||
|
self,
|
||||||
|
asg: &mut Asg,
|
||||||
|
at: Span,
|
||||||
|
) -> ObjectIndex<Ident> {
|
||||||
|
asg.create(Ident::new_abstract(at))
|
||||||
|
.add_edge_to(asg, self, Some(at))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in New Issue