derive_deftly_macros/framework.rs
1//! Core types and traits for parsing and expansion
2//!
3//! Also re-exports the names that the implementation wants.
4//!
5//! Should be included with `use super::framework::*`, not `crate::`,
6//! so that it works with `tests/directly.rs` too.
7
8pub use super::prelude::*;
9
10pub use super::boolean::*;
11pub use super::repeat::*;
12pub use super::syntax::*;
13
14pub(super) use super::paste;
15pub(super) use super::paste::{IdentFrag, IdentFragInfallible};
16
17pub(super) use general_context::{
18 DriverlessContext, GeneralContext, GeneralContextBuf, MissingContextError,
19};
20
21/// Context during expansion
22///
23/// References the driver, and digested information about it.
24/// Also represents where in the driver we are,
25/// including repetition context.
26//
27// Has to be Copy because otherwise the borrow checker doesn't understand
28// what's going on with ctx_buf in Template::expand.
29#[derive(Debug, Clone, Copy)]
30pub struct Context<'c> {
31 pub top: &'c syn::DeriveInput,
32 pub template_crate: &'c syn::Path,
33 pub template_name: Option<&'c syn::Path>,
34 pub pmetas: &'c meta::PreprocessedMetas,
35 pub variant: Option<&'c WithinVariant<'c>>,
36 pub field: Option<&'c WithinField<'c>>,
37 pub within_loop: WithinLoop,
38 pub pvariants: &'c [PreprocessedVariant<'c>],
39 pub defs: DefinitionsContext<'c>,
40}
41
42/// Part of the context relating to user-defined expansions and conditions
43#[derive(Debug, Clone, Copy, Default)]
44pub struct DefinitionsContext<'c> {
45 pub defs: Definitions<'c>,
46 pub nesting_depth: u16,
47 pub nesting_parent: Option<(
48 &'c DefinitionsContext<'c>, //
49 &'c DefinitionName,
50 )>,
51}
52
53#[derive(Debug)]
54pub struct PreprocessedVariant<'c> {
55 pub fields: &'c syn::Fields,
56 pub pmetas: &'c meta::PreprocessedMetas,
57 pub pfields: Vec<PreprocessedField>,
58}
59
60#[derive(Debug)]
61pub struct PreprocessedField {
62 pub pmetas: meta::PreprocessedMetas,
63}
64
65#[derive(Debug, Clone)]
66pub struct WithinVariant<'c> {
67 pub variant: Option<&'c syn::Variant>,
68 pub fields: &'c syn::Fields,
69 pub pmetas: &'c meta::PreprocessedMetas,
70 pub pfields: &'c [PreprocessedField],
71 pub index: u32,
72}
73
74#[derive(Debug, Clone)]
75pub struct WithinField<'c> {
76 pub field: &'c syn::Field,
77 pub pfield: &'c PreprocessedField,
78 pub index: u32,
79}
80
81/// Whether we're in a loop, and if so, its details
82///
83/// Set only for expansions of a `RepeatedTemplate`,
84/// not any kind of implicit looping eg `dbg_all_keywords`, `vpat`, etc.
85///
86/// At some future point this may have enough information
87/// to provide `$loop_index`, etc.
88/// Right now it's only used for `dbg_all_keywords`.
89#[derive(Debug, Clone, Copy)]
90pub enum WithinLoop {
91 None,
92 /// Evaluating `${when }` clauses
93 When,
94 /// Evaluating the body
95 Body,
96}
97
98#[derive(Debug, Clone, Copy, Default)]
99pub struct Definitions<'c> {
100 pub here: &'c [&'c Definition<DefinitionBody>],
101 pub conds: &'c [&'c Definition<DefCondBody>],
102 pub earlier: Option<&'c Definitions<'c>>,
103}
104
105/// Special processing instructions returned by
106/// [`before_element_hook`](SpecialParseContext::before_element_hook)
107pub enum SpecialInstructions {
108 /// This template is finished
109 ///
110 /// Stop parsing this `Template` though perhaps
111 /// the surrounding `Group` is not finished.
112 ///
113 /// The parser for whatever called `Template::parse`
114 /// will continue.
115 EndOfTemplate,
116}
117
118/// Surrounding lexical context during parsing
119///
120/// This is the kind of lexical context a piece of a template appears in.
121/// It is implemented for
122/// * Types that represent an expansion output `ExpansionOutput`;
123/// in this case, the lexical context is one where
124/// the expansion is accumulated in this type.
125/// * Places where template substitution syntax `${keyword }`
126/// appears but where no output will be generated (eg, within
127/// the condition of `${if }`.
128///
129/// The associated types are either `Void` or `()`.
130/// They appears within the variants of `SubstDetails`,
131/// causing inapplicable variants to be eliminated.
132///
133/// Because a variant is only inhabited if all of its fields are,
134/// the conditions are effectively ANDed.
135/// So the "default" value (for context that don't have an opnion)
136/// is inhabitedness `()`.
137///
138/// Each type has an associated constructur,
139/// used during parsing.
140/// So this generates a parse error at parse time,
141/// if a construct appears in the wrong place.
142pub trait SubstParseContext: Sized {
143 /// Uninhabited iff this lexical context is within `${paste }`
144 type NotInPaste: Debug + Copy + Sized;
145 /// Uninhabited iff this lexical context is within `${concat }`
146 type NotInConcat: Debug + Copy + Sized;
147 /// Uninhabited iff this lexical context is within a condition.
148 type NotInBool: Debug + Copy + Sized;
149 /// Uninhabited unless this lexical context is within `${concat }`
150 type ConcatOnly: Debug + Copy + Sized;
151 /// Uninhabited unless this lexical context is within a condition.
152 type BoolOnly: Debug + Copy + Sized;
153
154 /// Whether this is a boolean context
155 //
156 // Useful for ad-hoc handling of the way that boolean
157 // context has a different notion of syntax.
158 const IS_BOOL: bool = false;
159
160 /// Content of the `dbg` keyword
161 ///
162 /// This has to be in this trait because
163 /// `${dbg }` contains a `Template` but `dbg(...)` contains a `Subst`.
164 ///
165 /// For all `ExpansionContext` impls, should be `Template<Self>`.
166 // We'd use an associated type default, but they're unstable.
167 ///
168 /// We make bespoke output for each context; for boolean this is sui
169 /// generis, and for expansions it's in [`ExpansionOutput::dbg_expand`].
170 type DbgContent: Parse + Debug + AnalyseRepeat + meta::FindRecogMetas;
171
172 fn not_in_paste(span: &impl Spanned) -> syn::Result<Self::NotInPaste>;
173 fn not_in_concat(span: &impl Spanned) -> syn::Result<Self::NotInConcat>;
174 fn not_in_bool(span: &impl Spanned) -> syn::Result<Self::NotInBool>;
175
176 fn bool_only(span: &impl Spanned) -> syn::Result<Self::BoolOnly> {
177 Err(span.error(
178 "derive-deftly keyword is a condition - not valid as an expansion",
179 ))
180 }
181 fn concat_only(span: &impl Spanned) -> syn::Result<Self::ConcatOnly> {
182 Err(span
183 .error("derive-deftly keyword is only allowed within ${concat }"))
184 }
185
186 /// When we find a `fmeta` etc. in this context, does it allow a value?
187 ///
188 /// Used by the template-scanning code, to report whether an `Xmeta`
189 /// in the template justifies a value-bearing `Xmeta` attribute
190 /// on/in the driver, or just a boolean.
191 fn meta_recog_usage(m: &meta::SubstMeta<Self>) -> meta::Usage {
192 if m.default.is_some() {
193 meta::Usage::VALUE_ONLY
194 } else {
195 meta::Usage::VALUE
196 }
197 }
198
199 /// For communicating through `parse_special`
200 ///
201 /// Used when parsing `${paste ..}` to handle
202 /// the special `>`-based end condition.
203 type SpecialParseContext: SpecialParseContext + Default;
204
205 /// Parse using `f`, within parens in boolean context, not otherwise
206 ///
207 /// Useful for parsing the arguments to an argument-taking keyword
208 /// which takes an "equivalent" syntax in both contexts.
209 fn parse_maybe_within_parens<T>(
210 input: ParseStream,
211 f: impl FnOnce(ParseStream) -> syn::Result<T>,
212 ) -> syn::Result<T> {
213 if Self::IS_BOOL {
214 let inner;
215 let _ = parenthesized!(inner in input);
216 f(&inner)
217 } else {
218 f(input)
219 }
220 }
221
222 /// Parse maybe a comma (comma in boolean contegxt, not otherwise)
223 ///
224 /// Useful for parsing the arguments to an argument-taking keyword
225 /// which takes an "equivalent" syntax in both contexts.
226 fn parse_maybe_comma(input: ParseStream) -> syn::Result<()> {
227 if Self::IS_BOOL {
228 let _: Token![,] = input.parse()?;
229 }
230 Ok(())
231 }
232
233 /// Return an error suitable for reporting missing arguments
234 ///
235 /// Helper for handling missing arguments to an argument-taking keyword
236 /// which takes an "equivalent" syntax in both contexts.
237 fn missing_keyword_arguments(kw_span: Span) -> syn::Result<Void> {
238 Err(kw_span.error(format_args!(
239 "missing parameters to expansion keyword (NB: argument must be within {{ }})",
240 )))
241 }
242}
243
244pub trait SpecialParseContext {
245 /// Handle any special syntax for a special kind of template context.
246 ///
247 /// This method is called only when parsing multi-element [`Template`]s,
248 /// It's a hook, called before parsing each `TemplateElement`.
249 ///
250 /// It should consume any special syntax as appropriate,
251 ///
252 /// The default implementation is a no-op.
253 /// The only non-default implementation is in `paste.rs`, for `$<...>` -
254 /// see [`paste::AngleBrackets`].
255 fn before_element_hook(
256 &mut self,
257 _input: ParseStream,
258 ) -> syn::Result<Option<SpecialInstructions>>;
259}
260
261impl SpecialParseContext for () {
262 fn before_element_hook(
263 &mut self,
264 _input: ParseStream,
265 ) -> syn::Result<Option<SpecialInstructions>> {
266 Ok(None)
267 }
268}
269
270/// Inhabited iff this lexical content may expand to arbitrary tokens
271///
272/// Calculated automatically
273#[allow(type_alias_bounds)]
274pub type AllowTokens<O: SubstParseContext> = (
275 <O as SubstParseContext>::NotInPaste,
276 <O as SubstParseContext>::NotInConcat,
277);
278
279/// Extensions/helpers for `SubstParseContext`
280pub trait SubstParseContextExt: SubstParseContext {
281 fn allow_tokens(span: &impl Spanned) -> syn::Result<AllowTokens<Self>>;
282}
283impl<O: SubstParseContext> SubstParseContextExt for O {
284 fn allow_tokens(span: &impl Spanned) -> syn::Result<AllowTokens<Self>> {
285 let span = span.span();
286 Ok((O::not_in_paste(&span)?, (O::not_in_concat(&span)?)))
287 }
288}
289
290/// Expansion output accumulator, for a template lexical context
291///
292/// Each template lexical context has a distinct type which
293/// * Represents the lexical context
294/// * If that lexical context generates expansions,
295/// accumulates the expansion. That's what this trait is.
296///
297/// The methods are for accumulating various kinds of things
298/// that can be found in templates, or result from template expansion.
299///
300/// The accumulating type (`Self` might be accumulating
301/// tokens ([`TokenStream`]) or strings ([`paste::Items`]).
302pub trait ExpansionOutput: SubstParseContext {
303 /// An identifier (or fragment of one)
304 ///
305 /// Uses the `IdentFragment` for identifier pasting,
306 /// and the `ToTokens` for general expansion.
307 fn append_identfrag_toks<I: IdentFrag>(
308 &mut self,
309 ident: &I,
310 ) -> Result<(), I::BadIdent>;
311
312 /// Append a Rust path (scoped identifier, perhaps with generics)
313 ///
314 /// To facilitate `${pawte }`, the path is provided as:
315 /// * some prefix tokens (e.g., a scoping path),
316 /// * the actual identifer,
317 /// * some suffix tokens (e.g. generics).
318 ///
319 /// `tspan` is the span of the part of the template
320 /// which expanded into this path.
321 ///
322 /// This is a "more complex" expansion,
323 /// in the terminology of the template reference:
324 /// If a paste contains more than one, it is an error.
325 fn append_idpath<A, B, I>(
326 &mut self,
327 template_entry_span: Span,
328 pre: A,
329 ident: &I,
330 post: B,
331 grouping: Grouping,
332 ) -> Result<(), I::BadIdent>
333 where
334 A: FnOnce(&mut TokenAccumulator),
335 B: FnOnce(&mut TokenAccumulator),
336 I: IdentFrag;
337
338 /// Append a [`syn::LitStr`](struct@syn::LitStr)
339 ///
340 /// This is its own method because `syn::LitStr` is not `Display`,
341 /// and we don't want to unconditionally turn it into a string
342 /// before retokenising it.
343 fn append_syn_litstr(&mut self, v: &syn::LitStr);
344
345 /// Append a [`syn::Type`]
346 ///
347 /// This is a "more complex" expansion,
348 /// in the terminology of the template reference:
349 /// If a paste contains more than one, it is an error.
350 fn append_syn_type(
351 &mut self,
352 te_span: Span,
353 mut v: syn::Type,
354 mut grouping: Grouping,
355 ) {
356 loop {
357 let (inner, add_grouping) = match v {
358 syn::Type::Paren(inner) => (inner.elem, Grouping::Parens),
359 syn::Type::Group(inner) => (inner.elem, Grouping::Invisible),
360 _ => break,
361 };
362 v = *inner;
363 grouping = cmp::max(grouping, add_grouping);
364 }
365 if let syn::Type::Path(tp) = &mut v {
366 typepath_add_missing_argument_colons(tp, te_span);
367 }
368 self.append_syn_type_inner(te_span, v, grouping)
369 }
370
371 /// Append a [`syn::Type`], which has been grouping-normalised
372 fn append_syn_type_inner(
373 &mut self,
374 te_span: Span,
375 v: syn::Type,
376 grouping: Grouping,
377 );
378
379 /// Append using a function which generates tokens
380 ///
381 /// If you have an `impl `[`ToTokens`],
382 /// use [`append_tokens`](ExpansionOutput::append_tokens) instead.
383 ///
384 /// Not supported within `${paste }`.
385 /// The `NotInPaste` parameter makes this method unreachable
386 /// when expanding within `${paste }`;
387 /// or to put it another way,
388 /// it ensures that such an attempt would have been rejected
389 /// during template parsing.
390 fn append_tokens_with(
391 &mut self,
392 allow_tokens: &AllowTokens<Self>,
393 f: impl FnOnce(&mut TokenAccumulator) -> syn::Result<()>,
394 ) -> syn::Result<()>;
395
396 /// "Append" a substitution which can only be used within a boolean
397 ///
398 /// Such a thing cannot be expanded, so it cannot be appended,
399 /// so this function must be unreachable.
400 /// `expand_bool_only` is called (in expansion contexts)
401 /// to handle uninhabited `SubstDetails` variants etc.
402 ///
403 /// Implementing it involves demonstrating that
404 /// either `self`, or `Self::BoolOnly`, is uninhabited,
405 /// with a call to [`void::unreachable`].
406 fn append_bool_only(&mut self, bool_only: &Self::BoolOnly) -> !;
407
408 /// Note that an error occurred
409 ///
410 /// This must arrange to
411 /// (eventually) convert it using `into_compile_error`
412 /// and emit it somewhere appropriate.
413 fn record_error(&mut self, err: syn::Error);
414
415 /// Convenience method for noting an error with span and message
416 fn write_error<S: Spanned, M: Display>(&mut self, span: &S, message: M) {
417 self.record_error(span.error(message));
418 }
419
420 /// Convenience method for writing a `ToTokens`
421 ///
422 /// Dispatches to
423 /// [`append_tokens_with`](ExpansionOutput::append_tokens_with)
424 /// Not supported within `${paste }`.
425 //
426 // I experimented with unifying this with `append_tokens_with`
427 // using a `ToTokensFallible` trait, but it broke type inference
428 // rather badly and had other warts.
429 fn append_tokens(
430 &mut self,
431 allow_tokens: &AllowTokens<Self>,
432 tokens: impl ToTokens,
433 ) -> syn::Result<()> {
434 self.append_tokens_with(allow_tokens, |out| {
435 out.append(tokens);
436 Ok(())
437 })
438 }
439
440 /// Make a new empty expansion output, introduced at `kw_span`
441 ///
442 /// Normally, call sites use an inherent constructor method.
443 /// This one is used for special cases, eg `${ignore ...}`
444 fn new_with_span(kw_span: Span) -> Self;
445
446 fn default_subst_meta_as(kw: Span) -> syn::Result<meta::SubstAs<Self>> {
447 Err(kw.error("missing `as ...` in meta expansion"))
448 }
449
450 /// Implement the core of the `ignore` keyword
451 ///
452 /// If there was an error, returns it.
453 /// Otherwise, discards everything.
454 fn ignore_impl(self) -> syn::Result<()>;
455
456 /// Implement the `dbg` keyword
457 ///
458 /// Specifically:
459 /// * Write the expansion of `child` to `msg` in human-readable form
460 /// * Without a trailing newline
461 /// * Subsume it into `self`
462 ///
463 /// Expansion errors are to be reported, and subsumed into `self`.
464 /// Failures to write may be thrown.
465 fn dbg_expand<'c>(
466 &mut self,
467 kw_span: Span,
468 ctx: GeneralContext<'c>,
469 msg: &mut String,
470 content: &Self::DbgContent,
471 ) -> fmt::Result;
472}
473
474/// Convenience trait providing `item.expand()`
475///
476/// Implementations of this are often specific to the [`ExpansionOutput`].
477///
478/// Having this as a separate trait,
479/// rather than hanging it off `ExpansionOutput`,
480/// makes the expansion method more convenient to call.
481///
482/// It also avoids having to make all of these expansion methods
483/// members of the `ExpansionOutput` trait.
484pub trait Expand<O> {
485 fn expand<'c>(
486 &self,
487 ctx: GeneralContext<'c>,
488 out: &mut O,
489 ) -> syn::Result<()>;
490}
491/// Convenience trait providing `fn expand(self)`, infallible version
492///
493/// Some of our `expand` functions always record errors
494/// within the output accumulator
495/// and therefore do not need to return them.
496pub trait ExpandInfallible<O> {
497 fn expand<'c>(&self, ctx: GeneralContext<'c>, out: &mut O);
498}
499
500/// Accumulates tokens, or errors
501///
502/// We collect all the errors, and if we get an error, don't write
503/// anything out.
504/// This is because `compile_error!` (from `into_compile_error`)
505/// only works in certain places in Rust syntax (!)
506#[derive(Debug)]
507pub struct TokenAccumulator(Result<TokenStream, syn::Error>);
508
509impl<'c> Context<'c> {
510 pub fn is_enum(&self) -> bool {
511 matches!(self.top.data, syn::Data::Enum(_))
512 }
513
514 /// Description of the whole expansion, suitable for `dbg` option, etc.
515 pub fn expansion_description(&self) -> impl Display {
516 let ident = &self.top.ident;
517 if let Some(templ) = &self.template_name {
518 format!(
519 "derive-deftly expansion of {} for {}",
520 templ.to_token_stream(),
521 ident,
522 )
523 } else {
524 format!("derive-deftly expansion, for {}", ident,)
525 }
526 }
527}
528
529impl Default for TokenAccumulator {
530 fn default() -> Self {
531 TokenAccumulator(Ok(TokenStream::new()))
532 }
533}
534
535impl TokenAccumulator {
536 pub fn new() -> Self {
537 Self::default()
538 }
539 pub fn with_tokens<R>(
540 &mut self,
541 f: impl FnOnce(&mut TokenStream) -> R,
542 ) -> Option<R> {
543 self.0.as_mut().ok().map(f)
544 }
545 pub fn append(&mut self, t: impl ToTokens) {
546 self.with_tokens(|out| t.to_tokens(out));
547 }
548 pub fn tokens(self) -> syn::Result<TokenStream> {
549 self.0
550 }
551 /// Appends `val`, via [`ToTokensPunctComposable`] or [`ToTokens`]
552 pub fn append_maybe_punct_composable(
553 &mut self,
554 val: &(impl ToTokens + ToTokensPunctComposable),
555 composable: bool,
556 ) {
557 self.with_tokens(|out| {
558 if composable {
559 val.to_tokens_punct_composable(out);
560 } else {
561 val.to_tokens(out);
562 }
563 });
564 }
565}
566
567impl SubstParseContext for TokenAccumulator {
568 type NotInPaste = ();
569 type NotInConcat = ();
570 type NotInBool = ();
571 type DbgContent = Template<Self>;
572 fn not_in_paste(_: &impl Spanned) -> syn::Result<()> {
573 Ok(())
574 }
575 fn not_in_concat(_: &impl Spanned) -> syn::Result<()> {
576 Ok(())
577 }
578 fn not_in_bool(_: &impl Spanned) -> syn::Result<()> {
579 Ok(())
580 }
581
582 type BoolOnly = Void;
583 type ConcatOnly = Void;
584
585 type SpecialParseContext = ();
586}
587
588impl ExpansionOutput for TokenAccumulator {
589 fn append_identfrag_toks<I: IdentFrag>(
590 &mut self,
591 ident: &I,
592 ) -> Result<(), I::BadIdent> {
593 self.with_tokens(
594 |out| ident.frag_to_tokens(out), //
595 )
596 .unwrap_or(Ok(()))
597 }
598 fn append_idpath<A, B, I>(
599 &mut self,
600 _te_span: Span,
601 pre: A,
602 ident: &I,
603 post: B,
604 grouping: Grouping,
605 ) -> Result<(), I::BadIdent>
606 where
607 A: FnOnce(&mut TokenAccumulator),
608 B: FnOnce(&mut TokenAccumulator),
609 I: IdentFrag,
610 {
611 let inner = match self.with_tokens(|_outer| {
612 let mut inner = TokenAccumulator::new();
613 pre(&mut inner);
614 inner.append_identfrag_toks(ident)?;
615 post(&mut inner);
616 Ok(inner)
617 }) {
618 None => return Ok(()), // earlier errors, didn't process
619 Some(Err(e)) => return Err(e),
620 Some(Ok(ta)) => ta,
621 };
622
623 match inner.tokens() {
624 Ok(ts) => self.append(grouping.surround(ident.span(), ts)),
625 Err(e) => self.record_error(e),
626 }
627 Ok(())
628 }
629 fn append_syn_litstr(&mut self, lit: &syn::LitStr) {
630 self.append(lit);
631 }
632 fn append_syn_type_inner(
633 &mut self,
634 _te_span: Span,
635 ty: syn::Type,
636 grouping: Grouping,
637 ) {
638 self.append(grouping.surround(ty.span(), ty));
639 }
640 fn append_tokens_with(
641 &mut self,
642 (_not_in_paste, _not_in_concat): &((), ()),
643 f: impl FnOnce(&mut TokenAccumulator) -> syn::Result<()>,
644 ) -> syn::Result<()> {
645 f(self)
646 }
647
648 fn append_bool_only(&mut self, bool_only: &Self::BoolOnly) -> ! {
649 void::unreachable(*bool_only)
650 }
651
652 fn record_error(&mut self, err: syn::Error) {
653 if let Err(before) = &mut self.0 {
654 before.combine(err);
655 } else {
656 self.0 = Err(err)
657 }
658 }
659
660 fn new_with_span(_kw_span: Span) -> Self {
661 Self::new()
662 }
663
664 fn ignore_impl(self) -> syn::Result<()> {
665 self.0.map(|_: TokenStream| ())
666 }
667
668 fn dbg_expand<'c>(
669 &mut self,
670 _kw_span: Span,
671 ctx: GeneralContext<'c>,
672 msg: &mut String,
673 content: &Template<TokenAccumulator>,
674 ) -> fmt::Result {
675 let mut child = TokenAccumulator::new();
676 content.expand(ctx, &mut child);
677 let child = child.tokens();
678 match &child {
679 Err(e) => write!(msg, "/* ERROR: {} */", e)?,
680 Ok(y) => write!(msg, "{}", y)?,
681 }
682 match child {
683 Ok(y) => self.append(y),
684 Err(e) => self.record_error(e),
685 }
686 Ok(())
687 }
688}