tamer: asg: Support abstract independent clauses (for desc)

The number of files that had to be changed here emphasizes the need for a
better abstraction for indirection in the future.  I just don't have the
time to do so yet.

This introduces the same type of indirection that was needed for abstract
bindings: we need to be able to distinguish between a metavariable
identifier's lexical value as a _string_ vs. a _reference_; the former is
how it was interpreted previously, which meant that interpolation would not
cause an edge to be created to the metavariable, which in turn means that
the metavariable was not referenced.  This in turn blocked a change to
require that all metavariables be referenced.

I'm climbing back up my stash stack.

DEV-13163
main
Mike Gerwitz 2023-08-16 00:20:14 -04:00
parent 666b3d312f
commit 0b84772853
14 changed files with 441 additions and 21 deletions

View File

@ -859,6 +859,32 @@ sum_ir! {
), ),
}, },
/// Describe the active object using a reference to a
/// metavariable that will be expected to contain an independent
/// clause.
///
/// This is identical to [`Self::DocIndepClause`] in behavior
/// except for a level of indirection.
/// This reference allows for documentation generation via the
/// template system.
///
/// Implementation Note
/// -------------------
/// The system could be generalized by eliminating
/// [`Self::DocIndepClause`] in favor of this and always
/// requiring that a documentation literal be defined using a
/// metavariable literal;
/// that was not done because it would introduce a
/// significant number of additional objects to the graph.
DocIndepClauseRef(text: SPair) => {
span: text,
display: |f| write!(
f,
"reference to a metavariable describing the active \
object as a subject in a sentence",
),
},
/// Arbitrary documentation text. /// Arbitrary documentation text.
/// ///
/// TAMER hopes to eventually provide structured documentation, /// TAMER hopes to eventually provide structured documentation,

View File

@ -792,14 +792,21 @@ pub fn pkg_expect_ident_obj<O: ObjectRelatable + ObjectRelFrom<Ident>>(
pkg_expect_ident_oi(ctx, name).resolve(ctx.asg_ref()) pkg_expect_ident_oi(ctx, name).resolve(ctx.asg_ref())
} }
pub fn expect_ident_oi<O: ObjectRelatable + ObjectRelFrom<Ident>>(
ctx: &<AirAggregate as ParseState>::Context,
env: impl ObjectIndexRelTo<Ident>,
name: SPair,
) -> ObjectIndex<O> {
ctx.env_scope_lookup::<Ident>(env, name)
.expect("missing requested Ident `{name}`")
.definition_narrow::<O>(ctx.asg_ref())
.expect("missing `{name}` definition")
}
pub fn expect_ident_obj<O: ObjectRelatable + ObjectRelFrom<Ident>>( pub fn expect_ident_obj<O: ObjectRelatable + ObjectRelFrom<Ident>>(
ctx: &<AirAggregate as ParseState>::Context, ctx: &<AirAggregate as ParseState>::Context,
env: impl ObjectIndexRelTo<Ident>, env: impl ObjectIndexRelTo<Ident>,
name: SPair, name: SPair,
) -> &O { ) -> &O {
ctx.env_scope_lookup::<Ident>(env, name) expect_ident_oi(ctx, env, name).resolve(ctx.asg_ref())
.expect("missing requested Ident `{name}`")
.definition_narrow::<O>(ctx.asg_ref())
.expect("missing `{name}` definition")
.resolve(ctx.asg_ref())
} }

View File

@ -20,7 +20,9 @@
mod apply; mod apply;
use super::*; use super::*;
use crate::asg::air::test::{as_pkg_body, expect_ident_obj, Sut}; use crate::asg::air::test::{
as_pkg_body, expect_ident_obj, expect_ident_oi, Sut,
};
use crate::convert::ExpectInto; use crate::convert::ExpectInto;
use crate::span::dummy::*; use crate::span::dummy::*;
use crate::{ use crate::{
@ -966,3 +968,132 @@ fn expression_referencing_abstract_with_missing_is_abstract() {
let expr = expect_ident_obj::<Expr>(&ctx, oi_tpl, spair("expr", S21)); let expr = expect_ident_obj::<Expr>(&ctx, oi_tpl, spair("expr", S21));
assert_eq!(expr.meta_state(), MetaState::Abstract); assert_eq!(expr.meta_state(), MetaState::Abstract);
} }
// Independent clauses representing descriptions can also reference
// metavariables,
// allowing for documentation generation.
// Because such a clause requires expansion,
// and because it is owned by the object that it describes,
// it must cause the parent object to also be interpreted as abstract.
#[test]
fn abstract_doc_clause_binds_to_ref() {
#[rustfmt::skip]
let toks = [
TplStart(S1),
BindIdent(spair("_tpl_", S2)),
MetaStart(S3),
BindIdent(spair("@param@", S4)), // <-.
MetaEnd(S5), // |
// |
ExprStart(ExprOp::Sum, S7), // |
BindIdent(spair("expr", S8)), // |
// |
// This reference causes the parent // |
// expression to become abstract since // |
// it requires expansion. // |
DocIndepClauseRef(spair("@param@", S9)), // --'
ExprEnd(S11),
TplEnd(S12),
];
let ctx = air_ctx_from_pkg_body_toks(toks);
let asg = ctx.asg_ref();
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl_", S20));
// Even though the _calculation_ portion is concrete,
// the Doc object causes this expression to be abstract.
let oi_expr = expect_ident_oi::<Expr>(&ctx, oi_tpl, spair("expr", S21));
let expr = oi_expr.resolve(asg);
assert_eq!(expr.meta_state(), MetaState::Abstract);
let oi_doc = oi_expr
.edges_filtered::<Doc>(asg)
.next()
.expect("missing Doc object on Expr");
let doc = oi_doc.resolve(asg);
assert_eq!(&Doc::AbstractIndepClause(S9), doc);
let oi_ident = oi_doc
.edges_filtered::<Ident>(asg)
.next()
.expect("missing abstract doc ident");
assert_eq!(S4, oi_ident.resolve(asg).span());
}
// Independent clauses representing descriptions can also reference
// metavariables,
// allowing for documentation generation.
// Because such a clause requires expansion,
// and because it is owned by the object that it describes,
// it must cause the parent object to also be interpreted as abstract.
//
// It is not expected that the user will encounter this error in practice
// when using the source XML language,
// because interpolation ensures that documentation is either a literal
// or a proper metavariable reference.
// With that said,
// the intent is to have other IRs lower into AIR in the future.
#[test]
fn abstract_doc_clause_cannot_bind_to_non_meta_ref() {
#[rustfmt::skip]
let toks = [
TplStart(S1),
BindIdent(spair("_tpl_", S2)),
ExprStart(ExprOp::Sum, S3),
BindIdent(spair("notmeta", S4)), // <-.
ExprEnd(S5), // |
// |
ExprStart(ExprOp::Sum, S7), // |
BindIdent(spair("expr", S8)), // |
// |
// This references another expression. // |
DocIndepClauseRef(spair("notmeta", S9)), // --'
ExprEnd(S11),
TplEnd(S12),
];
let mut sut = parse_as_pkg_body(toks);
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Parsed::Incomplete), // PkgStart
Ok(Parsed::Incomplete), // TplStart
Ok(Parsed::Incomplete), // BindIdent
Ok(Parsed::Incomplete), // ExprStart
Ok(Parsed::Incomplete), // BindIdent
Ok(Parsed::Incomplete), // ExprEnd
Ok(Parsed::Incomplete), // ExprStart
Ok(Parsed::Incomplete), // BindIdent
Err(ParseError::StateError(
AsgError::InvalidDocRef(S9, S4)
)),
// RECOVERY: We build the expression,
// omitting the documentation string.
Ok(Parsed::Incomplete), // ExprEnd
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's make sure that the template created after recovery succeeded.
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl_", S20));
// We should have kept the expression despite the error.
assert!(oi_tpl
.edges_filtered::<Ident>(asg)
.filter_map(|oi_ident| oi_ident.definition_narrow::<Expr>(asg))
.any(|oi| oi.resolve(asg).span() == S7.merge(S11).unwrap()));
}

View File

@ -163,6 +163,14 @@ pub enum AsgError {
/// but it cannot be conveyed as documentation. /// but it cannot be conveyed as documentation.
InvalidDocContextMeta(ErrorOccurrenceSpan, ErrorContextSpan), InvalidDocContextMeta(ErrorOccurrenceSpan, ErrorContextSpan),
/// Abstract documentation references an identifier that is not bound to
/// a metavariable.
///
/// Documentation was expected to be derived from the lexical value of a
/// metavariable during expansion,
/// but the provided identifier does not reference a metavariable.
InvalidDocRef(ErrorOccurrenceSpan, ErrorTargetSpan),
/// A circular dependency was found where it is not permitted. /// A circular dependency was found where it is not permitted.
/// ///
/// A cycle almost always means that computing the value of an object /// A cycle almost always means that computing the value of an object
@ -204,6 +212,12 @@ type FirstOccurrenceSpan = Span;
/// this may refer to the container of the object that caused an error. /// this may refer to the container of the object that caused an error.
type ErrorContextSpan = Span; type ErrorContextSpan = Span;
/// The span representing the target of a reference.
///
/// It is expected that a corresponding [`ErrorOccurrenceSpan`] reference
/// the _source_ of the reference.
type ErrorTargetSpan = Span;
impl Display for AsgError { impl Display for AsgError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use AsgError::*; use AsgError::*;
@ -285,6 +299,11 @@ impl Display for AsgError {
(template parameters)" (template parameters)"
), ),
InvalidDocRef(_, _) => write!(
f,
"expected metavariable reference for abstract documentation",
),
UnsupportedCycle(cycle) => { UnsupportedCycle(cycle) => {
write!(f, "circular dependency: {cycle}") write!(f, "circular dependency: {cycle}")
} }
@ -489,6 +508,16 @@ impl Diagnostic for AsgError {
span.help("did you mean to enclose this in a `<text>` node?"), span.help("did you mean to enclose this in a `<text>` node?"),
], ],
InvalidDocRef(ref_span, target_span) => vec![
ref_span.error(
"expected metavariable reference \
(such as a template parameter)",
),
target_span
.note("documentation is referencing this identifier"),
target_span.help("this identifier is not a metavariable"),
],
UnsupportedCycle(cycle) => { UnsupportedCycle(cycle) => {
// The cycle description clearly describes the cycle, // The cycle description clearly describes the cycle,
// but in neutral terms, // but in neutral terms,

View File

@ -977,7 +977,7 @@ impl<O: ObjectKind> ObjectIndex<O> {
{ {
self.edges_filtered::<Doc>(asg) self.edges_filtered::<Doc>(asg)
.map(ObjectIndex::cresolve(asg)) .map(ObjectIndex::cresolve(asg))
.find_map(Doc::indep_clause) .find_map(Doc::concrete_indep_clause)
} }
} }

View File

@ -22,9 +22,10 @@
//! TODO: Document TAME's stance on documentation and literate programming, //! TODO: Document TAME's stance on documentation and literate programming,
//! much of which hasn't been able to be realized over the years. //! much of which hasn't been able to be realized over the years.
use super::prelude::*; use super::{ident::IdentDefinition, prelude::*, Ident, Meta};
use crate::{ use crate::{
parse::{util::SPair, Token}, asg::graph::ProposedRel,
parse::{prelude::Annotate, util::SPair, Token},
span::Span, span::Span,
}; };
use std::fmt::Display; use std::fmt::Display;
@ -49,6 +50,17 @@ pub enum Doc {
/// The intent is for this text to be mixed with sibling objects, /// The intent is for this text to be mixed with sibling objects,
/// in a style similar to that of literate programming. /// in a style similar to that of literate programming.
Text(SPair), Text(SPair),
/// An [`IndepClause`](Self::IndepClause)whose text will not be known
/// until expansion.
///
/// This object is expected to contain an edge to an identifier
/// representing a metavariable whose lexical value will serve as the
/// documentation text.
///
/// The associated span is the location at which the documentation was
/// defined.
AbstractIndepClause(Span),
} }
impl Display for Doc { impl Display for Doc {
@ -64,30 +76,115 @@ impl Doc {
Self::IndepClause(clause) Self::IndepClause(clause)
} }
/// Reference an identifier bound to a metavariable whose lexical value
/// will represent an independent clause as in
/// [`Self::new_indep_clause`].
pub fn new_indep_clause_ref(ref_span: Span) -> Self {
Self::AbstractIndepClause(ref_span)
}
/// Arbitrary text serving as documentation for sibling objects in a /// Arbitrary text serving as documentation for sibling objects in a
/// literate style. /// literate style.
pub fn new_text(text: SPair) -> Self { pub fn new_text(text: SPair) -> Self {
Self::Text(text) Self::Text(text)
} }
pub fn indep_clause(&self) -> Option<SPair> { pub fn concrete_indep_clause(&self) -> Option<SPair> {
match self { match self {
Self::IndepClause(spair) => Some(*spair), Self::IndepClause(spair) => Some(*spair),
Self::Text(_) => None, Self::Text(_) => None,
// An independent clause _will_ be available at some point,
// but is not yet.
Self::AbstractIndepClause(_) => None,
} }
} }
pub fn span(&self) -> Span { pub fn span(&self) -> Span {
match self { match self {
Self::IndepClause(spair) | Self::Text(spair) => spair.span(), Self::IndepClause(spair) | Self::Text(spair) => spair.span(),
Self::AbstractIndepClause(span) => *span,
} }
} }
} }
object_rel! { object_rel! {
/// Templates may expand into nearly any context, /// Documentation strings cannot contain children,
/// and must therefore be able to contain just about anything. /// but they can be abstract.
Doc -> { Doc -> {
// empty // References to identifiers representing metavariables are
// permitted for documentation generation.
cross Ident {
fn pre_add_edge(
asg: &mut Asg,
rel: ProposedRel<Self, Ident>,
commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError> {
match rel.to_oi.definition(asg) {
// Missing; we'll have to check once we support
// notification on definition.
None => Ok(commit(asg)),
// Only metavariable references are permitted.
Some(IdentDefinition::Meta(_)) => {
Ok(commit(asg))
},
Some(
IdentDefinition::Expr(_)
| IdentDefinition::Tpl(_)
) => Err(AsgError::InvalidDocRef(
// This is a cross edge and so should always be
// available
rel.ref_span.unwrap_or(rel.from_oi.span()),
rel.to_oi.resolve(asg).span(),
))
}
}
},
// TODO: This is not actually used.
// There seems to be a bug in the `min_specialization` feature
// that results in too narrow of a type inference on
// `ObjectIndexRelTo<OB> for ObjectIndexTo<OB>`,
// expecting `ProposedRel<Doc, Ident>` instead of
// `<ProposedRel<Doc, OB>` when there is only one `AsgRelMut`
// impl defined
// (when there's only `Ident` above).
// Remove this rel to see if it's still a problem,
// noting that the nightly version of Rust must be manually
// bumped.
cross Meta {
fn pre_add_edge(
_asg: &mut Asg,
rel: ProposedRel<Self, Meta>,
_commit: impl FnOnce(&mut Asg),
) -> Result<(), AsgError> {
diagnostic_panic!(
vec![
rel.from_oi.span()
.internal_error("cannot create edge from here..."),
rel.to_oi.span()
.internal_error("...to here")
],
"unsupported edge (see object/doc.rs for more information)"
)
}
},
}
}
impl ObjectIndex<Doc> {
/// The identifier that is expected to resolve to a metavariable whose
/// lexical value will become the documentation string.
///
/// This is applicable to [`Doc::AbstractIndepClause`].
/// If no such reference exists,
/// [`None`] will be returned.
pub fn abstract_indep_clause_ref(
&self,
asg: &Asg,
) -> Option<ObjectIndex<Ident>> {
self.edges_filtered::<Ident>(asg).next()
} }
} }

View File

@ -470,12 +470,12 @@ object_rel! {
), ),
None => { None => {
rel.from_oi.map_obj_inner(asg, |meta: MetaState| { rel.from_oi.map_obj_inner(asg, |metast: MetaState| {
// This is a cross edge and so this span must be // This is a cross edge and so this span must be
// available, but the types provided don't // available, but the types provided don't
// guarantee that. // guarantee that.
let span = rel.ref_span.unwrap_or(rel.to_oi.span()); let span = rel.ref_span.unwrap_or(rel.to_oi.span());
meta.found_missing(span) metast.found_missing(span)
}); });
} }
}; };
@ -514,6 +514,20 @@ object_rel! {
match doc { match doc {
Doc::IndepClause(_) => Ok(commit(asg)), Doc::IndepClause(_) => Ok(commit(asg)),
// Even though this expression's _calculation_ may not
// be abstract,
// it does own this documentation object and
// therefore the only route to its expansion is
// through this expression.
Doc::AbstractIndepClause(_) => {
rel.from_oi.map_obj_inner(
asg,
|metast: MetaState| metast.found_abstract(),
);
Ok(commit(asg))
}
// This maintains compatibility with the XLST-based // This maintains compatibility with the XLST-based
// system. // system.
// TODO: Inline documentation was prohibited within // TODO: Inline documentation was prohibited within

View File

@ -191,7 +191,8 @@ object_rel! {
let doc = rel.to_oi.resolve(asg); let doc = rel.to_oi.resolve(asg);
match doc { match doc {
Doc::IndepClause(_) => Ok(commit(asg)), Doc::IndepClause(_)
| Doc::AbstractIndepClause(_) => Ok(commit(asg)),
// It doesn't make sense to allow inline documentation // It doesn't make sense to allow inline documentation
// here, // here,

View File

@ -1117,6 +1117,8 @@ pub trait ObjectIndexTreeRelTo<OB: ObjectRelatable>:
/// simple sentence or as part of a compound sentence. /// simple sentence or as part of a compound sentence.
/// There should only be one such clause for any given object, /// There should only be one such clause for any given object,
/// but that is not enforced here. /// but that is not enforced here.
///
/// See also [`Self::add_desc_short`] for abstract descriptions.
fn add_desc_short( fn add_desc_short(
self, self,
asg: &mut Asg, asg: &mut Asg,
@ -1130,6 +1132,25 @@ pub trait ObjectIndexTreeRelTo<OB: ObjectRelatable>:
Ok(self) Ok(self)
} }
/// Reference an identifier bound to a metavariable whose lexical value
/// will represent a short independent clause.
///
/// See also [`Self::add_desc_short`] for concrete descriptions.
fn add_desc_short_ref(
self,
asg: &mut Asg,
oi_ident: ObjectIndex<Ident>,
ref_span: Span,
) -> Result<Self, AsgError>
where
Self: ObjectIndexTreeRelTo<Doc>,
{
let oi_doc = asg.create(Doc::new_indep_clause_ref(ref_span));
oi_doc.add_cross_edge_to(asg, oi_ident, ref_span)?;
asg.add_tree_edge(self, oi_doc)?;
Ok(self)
}
/// Arbitrary text serving as documentation in a literate style, /// Arbitrary text serving as documentation in a literate style,
/// to be expanded into the application site. /// to be expanded into the application site.
fn append_doc_text( fn append_doc_text(

View File

@ -613,10 +613,32 @@ impl<'a> TreeContext<'a> {
Some(attr_desc(*desc)) Some(attr_desc(*desc))
} }
// @desc is still abstract and contains a metavariable reference;
// just output the name of the metavariable
(
Object::Expr(..) | Object::Tpl(..),
Doc::AbstractIndepClause(_),
) => {
let name = oi_doc
.abstract_indep_clause_ref(self.asg)
.and_then(|oi_ident| oi_ident.resolve(self.asg).name())
.diagnostic_expect(
|| {
vec![doc.span().internal_error(
"missing name for metavariable reference",
)]
},
"unable to resolve doc metavariable reference",
);
Some(attr_desc(name))
}
// template/param/@desc // template/param/@desc
(Object::Meta(_), Doc::IndepClause(_desc)) (
if self.tpl_apply.is_none() => Object::Meta(_),
{ Doc::IndepClause(_) | Doc::AbstractIndepClause(_),
) if self.tpl_apply.is_none() => {
// This is already covered in `emit_tpl_param` // This is already covered in `emit_tpl_param`
None None
} }
@ -630,12 +652,12 @@ impl<'a> TreeContext<'a> {
None None
} }
_ => { (_, doc) => {
diagnostic_todo!( diagnostic_todo!(
vec![oi_doc.internal_error( vec![oi_doc.internal_error(
"this documentation is not supported in XIRF output" "this documentation is not supported in XIRF output"
)], )],
"unsupported documentation", "unsupported documentation: {doc:?}",
) )
} }
} }

View File

@ -160,6 +160,12 @@ pub enum Nir {
/// Describe the [`NirEntity`] atop of the stack. /// Describe the [`NirEntity`] atop of the stack.
Desc(SPair), Desc(SPair),
/// Describe the [`NirEntity`] atop of the stack using a lexical
/// reference to a metavariable.
///
/// This allows for documentation generation.
DescRef(SPair),
/// A string literal. /// A string literal.
/// ///
/// The meaning of this string depends on context. /// The meaning of this string depends on context.
@ -223,6 +229,10 @@ impl Nir {
// expansion. // expansion.
BindIdentAbstract(_) => None, BindIdentAbstract(_) => None,
// Lexical references for descriptions do not become concrete
// until expansion.
DescRef(_) => None,
Noop(_) => None, Noop(_) => None,
} }
} }
@ -257,6 +267,7 @@ impl Map<SymbolId> for Nir {
RefSubject(spair) => RefSubject(spair.map(f)), RefSubject(spair) => RefSubject(spair.map(f)),
Ref(spair) => Ref(spair.map(f)), Ref(spair) => Ref(spair.map(f)),
Desc(spair) => Desc(spair.map(f)), Desc(spair) => Desc(spair.map(f)),
DescRef(spair) => DescRef(spair.map(f)),
Text(spair) => Text(spair.map(f)), Text(spair) => Text(spair.map(f)),
Import(spair) => Import(spair.map(f)), Import(spair) => Import(spair.map(f)),
@ -386,6 +397,7 @@ impl Token for Nir {
| RefSubject(spair) | RefSubject(spair)
| Ref(spair) | Ref(spair)
| Desc(spair) | Desc(spair)
| DescRef(spair)
| Text(spair) | Text(spair)
| Import(spair) => spair.span(), | Import(spair) => spair.span(),
@ -439,6 +451,12 @@ impl Display for Nir {
// TODO: TtQuote doesn't yet escape quotes at the time of writing! // TODO: TtQuote doesn't yet escape quotes at the time of writing!
Desc(spair) => write!(f, "description {}", TtQuote::wrap(spair)), Desc(spair) => write!(f, "description {}", TtQuote::wrap(spair)),
DescRef(spair) => write!(
f,
"abstract description referencing identifier {}",
TtQuote::wrap(spair)
),
// TODO: Not yet safe to output arbitrary text; // TODO: Not yet safe to output arbitrary text;
// need to determine how to handle newlines and other types of // need to determine how to handle newlines and other types of
// output. // output.

View File

@ -294,6 +294,9 @@ impl ParseState for NirToAir {
(st @ (Ready | Meta(_)), Desc(clause)) => { (st @ (Ready | Meta(_)), Desc(clause)) => {
Transition(st).ok(Air::DocIndepClause(clause)) Transition(st).ok(Air::DocIndepClause(clause))
} }
(st @ (Ready | Meta(_)), DescRef(spair)) => {
Transition(st).ok(Air::DocIndepClauseRef(spair))
}
(Ready, Import(namespec)) => { (Ready, Import(namespec)) => {
Transition(Ready).ok(Air::PkgImport(namespec)) Transition(Ready).ok(Air::PkgImport(namespec))

View File

@ -471,6 +471,13 @@ impl ParseState for InterpState {
// since its name will not be known until expansion-time. // since its name will not be known until expansion-time.
Nir::BindIdent(x) => Nir::BindIdentAbstract(x), Nir::BindIdent(x) => Nir::BindIdentAbstract(x),
// Descriptions must also be converted into an explicit
// reference,
// otherwise the new metavariable reference will be
// interpreted as the documentation string rather
// than a reference to one.
Nir::Desc(x) => Nir::DescRef(x),
// All other tokens only have their symbols replaced by // All other tokens only have their symbols replaced by
// the above. // the above.
x => x, x => x,

View File

@ -195,6 +195,50 @@ fn concrete_bind_ident_desugars_into_abstract_bind_after_interpolation() {
); );
} }
// Similar to the distinction of an abstract bind above,
// we must also make a distinction of a reference to a documentation
// string.
// The reason for this is that the reference would otherwise be taken
// (lexically) to be the value of the documentation string;
// we require that it represent indirection,
// and must indicate to the system in some way that the lexeme is in
// fact an identifier and not a string.
#[test]
fn doc_desc_desugars_into_desc_ref_after_interpolation() {
let given_val = "{@iamref@}";
// |[------]|
// |1 8|
// | B |
// [--------]
// 0 9
// A
// Non-zero span offset ensures that derived spans properly consider
// parent offset.
let a = DC.span(10, 10);
let b = DC.span(11, 8);
let given_sym = Nir::Desc(SPair(given_val.into(), a));
let toks = vec![given_sym];
let mut sut = Sut::parse(toks.into_iter());
let expect_name = expect_expanded_header(&mut sut, a);
assert_eq!(
Ok(vec![
// The interpolation occurs the same as above.
Object(Nir::Ref(SPair("@iamref@".into(), b))),
Object(Nir::Close(NirEntity::TplParam, a)),
// We translate the original `Desc` into a `DescRef`,
// indicating that the lexical value is to be interpreted as
// an identifier reference rather than a documentation string.
Object(Nir::DescRef(SPair(expect_name, a))),
]),
sut.collect(),
);
}
// When ending with an interpolated variable, // When ending with an interpolated variable,
// the parser should recognize that we've returned to the outer literal // the parser should recognize that we've returned to the outer literal
// context and permit successful termination of the specification string. // context and permit successful termination of the specification string.