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}