Skip to main content

dataflow_rs_derive/
lib.rs

1//! The proc-macro behind `#[derive(FunctionConfig)]`: a derive must live in
2//! a proc-macro crate of its own, and one derive serves the built-ins and
3//! hosts alike.
4//!
5//! The generated code names nothing but `::dataflow_rs::function::__private`,
6//! `::core` and `::std`, so the library must be reachable as `::dataflow_rs`.
7//! A dependent keeps the dependency key `dataflow-rs`, and renames v3, not
8//! v4, when both are in scope. Inside the crate, `extern crate self as
9//! dataflow_rs` does it.
10
11use proc_macro::TokenStream;
12use proc_macro2::TokenStream as TokenStream2;
13use quote::{format_ident, quote, quote_spanned};
14use syn::ext::IdentExt;
15use syn::parse::ParseStream;
16use syn::spanned::Spanned;
17use syn::{
18    Attribute, Data, DeriveInput, Error, Expr, ExprLit, Fields, GenericArgument, Ident, Lit,
19    LitStr, Meta, Path, PathArguments, Result, Token, Type,
20};
21
22/// Derives `FunctionConfig` for a function's typed configuration, and
23/// `ConfigField`, so a derived config nests inside another.
24///
25/// It applies to a struct with named fields. Each field is compiled from the
26/// key of its name through the field type's `ConfigField` implementation,
27/// every issue at its own path, unknown keys first.
28///
29/// | Attribute | On | Meaning |
30/// | --- | --- | --- |
31/// | `#[config(rename = "k")]` | field | The JSON key; the default is the field's name |
32/// | `#[config(default = <literal>)]` | field | A string, integer, float or bool, compiled through the field's type when the key is absent |
33/// | `#[config(default_json = "<json>")]` | field | Any JSON default, as text; text that is not JSON is a compile error |
34/// | `#[config(serde)]` | field | Read with `serde_json`; an error is `INPUT_PARSE`, and an absent key is read from `null`. Resolved as it serializes, so the type is `Serialize` too |
35/// | `#[config(records)]` | field | The engine records the field's result; inherited by everything below it |
36/// | `#[output(default = "p")]` | output | The default when the key is absent or `null` |
37/// | `#[output(primary)]` | output | Required on exactly one output when there are several |
38/// | `#[config(check = path)]` | struct | `fn(&Self, &mut CompileCx<'_>)`, called only when every field compiled |
39///
40/// A field is an output when it carries `#[output(...)]`, or its type's last
41/// path segment is `Output`, or it is an `Option` of such a type. A field's
42/// doc comment is its description.
43///
44/// The primary output, the one marked or the only one, is what
45/// `FunctionConfig::primary_output` returns, so its type is `Output` or
46/// `Option<Output>`; any other is refused.
47///
48/// `FunctionConfig::resolve` gives every field with a value, set by the
49/// input or by its default, keyed by its key, in declaration order, each
50/// through its type's `ConfigField::resolve`, and a `serde` field as it
51/// serializes; an `Option` left unset is absent.
52#[proc_macro_derive(FunctionConfig, attributes(config, output))]
53pub fn derive_function_config(input: TokenStream) -> TokenStream {
54    let input = syn::parse_macro_input!(input as DeriveInput);
55    expand(&input)
56        .unwrap_or_else(Error::into_compile_error)
57        .into()
58}
59
60/// One field, as its attributes declare it.
61struct Field<'a> {
62    ident: &'a Ident,
63    ty: &'a Type,
64    key: String,
65    /// The default as JSON text.
66    default: Option<String>,
67    serde: bool,
68    records: bool,
69    output: bool,
70    primary: bool,
71    description: Option<String>,
72}
73
74fn expand(input: &DeriveInput) -> Result<TokenStream2> {
75    let name = &input.ident;
76    // A raw identifier names its key and its messages without the `r#`.
77    let shown = name.unraw();
78    let named = match &input.data {
79        Data::Struct(data) => match &data.fields {
80            Fields::Named(named) => &named.named,
81            _ => return Err(not_a_struct(input)),
82        },
83        _ => return Err(not_a_struct(input)),
84    };
85    if !input.generics.params.is_empty() {
86        return Err(Error::new_spanned(
87            &input.generics,
88            "`#[derive(FunctionConfig)]` does not support generic parameters",
89        ));
90    }
91    let check = struct_check(&input.attrs)?;
92
93    let mut errors: Option<Error> = None;
94    let mut fields = Vec::new();
95    for field in named {
96        match parse_field(field) {
97            Ok(f) => fields.push(f),
98            Err(e) => push_error(&mut errors, e),
99        }
100    }
101    for (i, f) in fields.iter().enumerate() {
102        if fields[..i].iter().any(|g| g.key == f.key) {
103            push_error(
104                &mut errors,
105                Error::new_spanned(f.ident, format!("the key `{}` is used twice", f.key)),
106            );
107        }
108    }
109    let outputs = fields.iter().filter(|f| f.output).count();
110    let primaries = fields.iter().filter(|f| f.primary).count();
111    if outputs > 1 && primaries != 1 {
112        push_error(
113            &mut errors,
114            Error::new_spanned(
115                name,
116                format!(
117                    "`{shown}` has {outputs} outputs: mark exactly one of them `#[output(primary)]`"
118                ),
119            ),
120        );
121    }
122    if let Some(errors) = errors {
123        return Err(errors);
124    }
125    if outputs == 1 {
126        for f in fields.iter_mut().filter(|f| f.output) {
127            f.primary = true;
128        }
129    }
130    let primary = fields
131        .iter()
132        .find(|f| f.primary)
133        .map(|f| primary_shape(f).map(|optional| (f.ident, optional)))
134        .transpose()?;
135
136    Ok(generate(name, check.as_ref(), &fields, primary))
137}
138
139/// Whether the primary output `f` is an `Option<Output>` rather than an
140/// `Output`; an error for any other type.
141fn primary_shape(f: &Field<'_>) -> Result<bool> {
142    if is_output_type(f.ty) {
143        Ok(false)
144    } else if is_option_of_output(f.ty) {
145        Ok(true)
146    } else {
147        Err(Error::new_spanned(
148            f.ty,
149            format!(
150                "the primary output `{}` must be an `Output` or an `Option<Output>`",
151                f.ident.unraw()
152            ),
153        ))
154    }
155}
156
157fn not_a_struct(input: &DeriveInput) -> Error {
158    Error::new_spanned(
159        &input.ident,
160        "`#[derive(FunctionConfig)]` applies to a struct with named fields; for any other \
161         type, derive `serde::Deserialize` and `serde::Serialize`, and read it through a \
162         `#[config(serde)]` field",
163    )
164}
165
166fn push_error(errors: &mut Option<Error>, error: Error) {
167    match errors {
168        Some(errors) => errors.combine(error),
169        None => *errors = Some(error),
170    }
171}
172
173/// The struct's `#[config(check = path)]`, if any.
174fn struct_check(attrs: &[Attribute]) -> Result<Option<Path>> {
175    let mut check = None;
176    for attr in attrs {
177        if attr.path().is_ident("output") {
178            return Err(Error::new_spanned(attr, "`#[output]` goes on a field"));
179        }
180        if !attr.path().is_ident("config") {
181            continue;
182        }
183        attr.parse_nested_meta(|meta| {
184            if meta.path.is_ident("check") {
185                if check.is_some() {
186                    return Err(meta.error("`check` is declared twice"));
187                }
188                check = Some(meta.value()?.parse::<Path>()?);
189                Ok(())
190            } else {
191                Err(meta.error("on a struct, `config` takes only `check = path`"))
192            }
193        })?;
194    }
195    Ok(check)
196}
197
198fn parse_field(field: &syn::Field) -> Result<Field<'_>> {
199    let Some(ident) = field.ident.as_ref() else {
200        return Err(Error::new_spanned(field, "a field needs a name"));
201    };
202    let mut f = Field {
203        ident,
204        ty: &field.ty,
205        // `r#type` is the key `type`.
206        key: ident.unraw().to_string(),
207        default: None,
208        serde: false,
209        records: false,
210        output: false,
211        primary: false,
212        description: None,
213    };
214    let mut output_attr = false;
215    let mut docs = Vec::new();
216    // Each `config(…)` and `output(…)` key this field has given, across all of
217    // its attributes: a repeat is refused, as a second `default` is.
218    let mut given: Vec<String> = Vec::new();
219    let mut once = |attr: &str, meta: &syn::meta::ParseNestedMeta<'_>| -> Result<()> {
220        let key = meta
221            .path
222            .get_ident()
223            .map_or_else(String::new, ToString::to_string);
224        let seen = format!("{attr}({key})");
225        if given.contains(&seen) {
226            return Err(meta.error(format!("`{key}` is given twice on this field")));
227        }
228        given.push(seen);
229        Ok(())
230    };
231    for attr in &field.attrs {
232        if attr.path().is_ident("config") {
233            attr.parse_nested_meta(|meta| {
234                once("config", &meta)?;
235                if meta.path.is_ident("rename") {
236                    f.key = meta.value()?.parse::<LitStr>()?.value();
237                } else if meta.path.is_ident("default") {
238                    let json = literal_json(meta.value()?)?;
239                    set_default(&mut f.default, json, &meta)?;
240                } else if meta.path.is_ident("default_json") {
241                    let json = json_text(&meta.value()?.parse::<LitStr>()?)?;
242                    set_default(&mut f.default, json, &meta)?;
243                } else if meta.path.is_ident("serde") {
244                    f.serde = true;
245                } else if meta.path.is_ident("records") {
246                    f.records = true;
247                } else if meta.path.is_ident("check") {
248                    return Err(meta.error("`check` goes on the struct"));
249                } else {
250                    return Err(meta.error(
251                        "unknown `config` attribute: expected `rename`, `default`, \
252                         `default_json`, `serde` or `records`",
253                    ));
254                }
255                Ok(())
256            })?;
257        } else if attr.path().is_ident("output") {
258            output_attr = true;
259            attr.parse_nested_meta(|meta| {
260                once("output", &meta)?;
261                if meta.path.is_ident("default") {
262                    let path = meta.value()?.parse::<LitStr>()?.value();
263                    set_default(&mut f.default, json_string(&path), &meta)?;
264                } else if meta.path.is_ident("primary") {
265                    f.primary = true;
266                } else {
267                    return Err(meta.error(
268                        "unknown `output` attribute: expected `default = \"path\"` or `primary`",
269                    ));
270                }
271                Ok(())
272            })?;
273        } else if attr.path().is_ident("doc")
274            && let Meta::NameValue(doc) = &attr.meta
275            && let Expr::Lit(ExprLit {
276                lit: Lit::Str(text),
277                ..
278            }) = &doc.value
279        {
280            docs.push(text.value());
281        }
282    }
283    if f.serde && output_attr {
284        return Err(Error::new_spanned(
285            ident,
286            "a `#[config(serde)]` field cannot be an `#[output]`",
287        ));
288    }
289    if f.serde && f.records {
290        return Err(Error::new_spanned(
291            ident,
292            "a `#[config(serde)]` field holds no expression, so it cannot be \
293             `#[config(records)]`",
294        ));
295    }
296    f.output = output_attr || (!f.serde && (is_output_type(f.ty) || is_option_of_output(f.ty)));
297    let lines: Vec<&str> = docs
298        .iter()
299        .flat_map(|doc| doc.lines())
300        .map(str::trim)
301        .filter(|line| !line.is_empty())
302        .collect();
303    if !lines.is_empty() {
304        f.description = Some(lines.join(" "));
305    }
306    Ok(f)
307}
308
309fn set_default(
310    slot: &mut Option<String>,
311    json: String,
312    meta: &syn::meta::ParseNestedMeta<'_>,
313) -> Result<()> {
314    if slot.is_some() {
315        return Err(meta.error("a field takes one default"));
316    }
317    *slot = Some(json);
318    Ok(())
319}
320
321/// Does the type's last path segment name `Output`?
322fn is_output_type(ty: &Type) -> bool {
323    match ty {
324        Type::Path(path) => {
325            path.qself.is_none()
326                && path
327                    .path
328                    .segments
329                    .last()
330                    .is_some_and(|s| s.ident == "Output")
331        }
332        _ => false,
333    }
334}
335
336/// Is the type an `Option` of a type whose last path segment is `Output`?
337fn is_option_of_output(ty: &Type) -> bool {
338    let Type::Path(path) = ty else {
339        return false;
340    };
341    let Some(last) = path.path.segments.last() else {
342        return false;
343    };
344    if path.qself.is_some() || last.ident != "Option" {
345        return false;
346    }
347    match &last.arguments {
348        PathArguments::AngleBracketed(args) => {
349            args.args.len() == 1
350                && matches!(
351                    args.args.first(),
352                    Some(GenericArgument::Type(inner)) if is_output_type(inner)
353                )
354        }
355        _ => false,
356    }
357}
358
359/// A `default = <literal>` as JSON text: a string, an integer or float
360/// (optionally negative), or a bool.
361fn literal_json(input: ParseStream<'_>) -> Result<String> {
362    let negative = input.peek(Token![-]);
363    if negative {
364        input.parse::<Token![-]>()?;
365    }
366    let sign = if negative { "-" } else { "" };
367    match input.parse::<Lit>()? {
368        Lit::Str(s) if !negative => Ok(json_string(&s.value())),
369        Lit::Bool(b) if !negative => Ok(b.value().to_string()),
370        Lit::Int(i) => Ok(format!("{sign}{}", i.base10_digits())),
371        // Through `f64` so the text is JSON: Rust's `1.` and `1_0.5` are not.
372        Lit::Float(f) => {
373            let value = f.base10_parse::<f64>()?;
374            if !value.is_finite() {
375                return Err(Error::new_spanned(
376                    f,
377                    "a float `default` must be finite; JSON has no infinity",
378                ));
379            }
380            Ok(format!("{sign}{value:?}"))
381        }
382        other => Err(Error::new_spanned(
383            other,
384            "`default` takes a string, integer, float or bool literal; use `default_json` \
385             for anything else",
386        )),
387    }
388}
389
390/// A `default_json`'s text, refused here when it is not JSON, so the
391/// generated code parses it infallibly.
392fn json_text(lit: &LitStr) -> Result<String> {
393    let text = lit.value();
394    match serde_json::from_str::<serde_json::Value>(&text) {
395        Ok(_) => Ok(text),
396        Err(e) => Err(Error::new_spanned(
397            lit,
398            format!("`default_json` is not JSON ({e}): {text}"),
399        )),
400    }
401}
402
403/// `s` as a JSON string literal.
404fn json_string(s: &str) -> String {
405    serde_json::to_string(s).expect("a string always serializes as JSON")
406}
407
408fn option<T: quote::ToTokens>(value: Option<T>) -> TokenStream2 {
409    match value {
410        Some(value) => quote!(::core::option::Option::Some(#value)),
411        None => quote!(::core::option::Option::None),
412    }
413}
414
415/// The expansion. `primary` is the primary output's field, and whether it is
416/// an `Option<Output>`.
417fn generate(
418    name: &Ident,
419    check: Option<&Path>,
420    fields: &[Field<'_>],
421    primary: Option<(&Ident, bool)>,
422) -> TokenStream2 {
423    let p = quote!(::dataflow_rs::function::__private);
424    let keys: Vec<&str> = fields.iter().map(|f| f.key.as_str()).collect();
425    let vars: Vec<Ident> = (0..fields.len()).map(|i| format_ident!("__f{i}")).collect();
426    let idents = fields.iter().map(|f| f.ident);
427
428    let compiles = fields.iter().zip(&vars).map(|(f, var)| {
429        let (ty, key, output) = (f.ty, &f.key, f.output);
430        let default = option(f.default.as_deref());
431        let helper = if f.serde {
432            quote!(serde_field)
433        } else {
434            quote!(field)
435        };
436        quote! {
437            let #var = #p::#helper::<#ty>(
438                __object,
439                #p::Key::new(#key, #default, #output),
440                cx,
441            );
442        }
443    });
444    let assemble = if fields.is_empty() {
445        quote!()
446    } else {
447        quote! {
448            let (#(::core::option::Option::Some(#vars),)*) = (#(#vars,)*) else {
449                return ::core::option::Option::None;
450            };
451        }
452    };
453    let check = check.map(|check| quote!(#check(&__out, cx);));
454
455    let visits: Vec<TokenStream2> = fields
456        .iter()
457        .filter(|f| !f.serde)
458        .map(|f| {
459            let (ident, key, records) = (f.ident, &f.key, f.records);
460            quote! {
461                v.at(#key, |v| v.recording(#records, |v| {
462                    #p::ConfigField::visit(&self.#ident, v);
463                }));
464            }
465        })
466        .collect();
467    let v = if visits.is_empty() {
468        quote!(_v)
469    } else {
470        quote!(v)
471    };
472
473    let specs = fields.iter().map(|f| {
474        let (ty, key, records, primary) = (f.ty, &f.key, f.records, f.primary);
475        let kind = if f.serde {
476            quote!(#p::FieldKind::Literal(#p::ValueKind::Any))
477        } else {
478            quote!(<#ty as #p::ConfigField>::kind())
479        };
480        let required = if f.default.is_some() {
481            quote!(false)
482        } else if f.serde {
483            quote!(#p::serde_required::<#ty>())
484        } else {
485            quote!(<#ty as #p::ConfigField>::absent().is_none())
486        };
487        let default = option(f.default.as_deref());
488        let description = option(f.description.as_deref());
489        quote! {
490            #p::spec(#key, #kind, #required, #records, #default, #primary, #description)
491        }
492    });
493
494    // Every field in declaration order: a `serde` one through `Serialize`,
495    // spanned at its type so a missing impl is reported there.
496    let resolves: Vec<TokenStream2> = fields
497        .iter()
498        .map(|f| {
499            let (ident, key) = (f.ident, &f.key);
500            if f.serde {
501                quote_spanned! {f.ty.span()=>
502                    #p::resolve_serde(&mut __entries, #key, &self.#ident)?;
503                }
504            } else {
505                quote! {
506                    #p::resolve_field(&mut __entries, #key, &self.#ident, r)?;
507                }
508            }
509        })
510        .collect();
511    let r = if fields.iter().any(|f| !f.serde) {
512        quote!(r)
513    } else {
514        quote!(_r)
515    };
516    let capacity = fields.len();
517    let resolve = if fields.is_empty() {
518        quote!(::core::result::Result::Ok(#p::OwnedDataValue::Object(
519            ::std::vec::Vec::new()
520        )))
521    } else {
522        quote! {
523            let mut __entries: #p::Entries = ::std::vec::Vec::with_capacity(#capacity);
524            #(#resolves)*
525            ::core::result::Result::Ok(#p::OwnedDataValue::Object(__entries))
526        }
527    };
528
529    let primary_output = primary.map(|(ident, optional)| {
530        let output = if optional {
531            quote!(::core::option::Option::as_ref(&self.#ident))
532        } else {
533            quote!(::core::option::Option::Some(&self.#ident))
534        };
535        quote! {
536            fn primary_output(&self) -> ::core::option::Option<&#p::Output> {
537                #output
538            }
539        }
540    });
541
542    // A field the derive reads as an output must be an `Output` (or an
543    // `Option` of one): `IS_OUTPUT` is what the type says, so a type that
544    // merely shares the name fails here instead of compiling as a
545    // destination.
546    let assertions: Vec<TokenStream2> = fields
547        .iter()
548        .filter(|f| !f.serde)
549        .map(|f| {
550            let (ty, output) = (f.ty, f.output);
551            quote_spanned! {ty.span()=>
552                const _: () = ::core::assert!(
553                    !#output || <#ty as #p::ConfigField>::IS_OUTPUT,
554                    "a field the derive reads as an output must be an `Output` or an `Option<Output>`"
555                );
556            }
557        })
558        .collect();
559
560    quote! {
561        #[automatically_derived]
562        impl #p::FunctionConfig for #name {
563            fn compile(
564                input: &#p::Value,
565                cx: &mut #p::CompileCx<'_>,
566            ) -> ::core::option::Option<Self> {
567                const KNOWN: &[&str] = &[#(#keys),*];
568                let __rejected_before = cx.rejected();
569                let __object = #p::object(input, cx)?;
570                #p::unknown_keys(__object, KNOWN, cx);
571                #(#compiles)*
572                #assemble
573                let __out = Self { #(#idents: #vars,)* };
574                #check
575                #p::finish(__out, __rejected_before, cx)
576            }
577
578            fn visit(&self, v: &mut dyn #p::ExprVisitor) {
579                let mut v = #p::Visit::new(v, #p::AuthoredPath::root());
580                <Self as #p::ConfigField>::visit(self, &mut v);
581            }
582
583            fn fields() -> &'static [#p::FieldSpec] {
584                static FIELDS: #p::OnceLock<::std::vec::Vec<#p::FieldSpec>> =
585                    #p::OnceLock::new();
586                FIELDS.get_or_init(|| ::std::vec![#(#specs),*])
587            }
588
589            #primary_output
590
591            fn resolve(
592                &self,
593                #r: &mut dyn #p::Resolver,
594            ) -> #p::Result<#p::OwnedDataValue> {
595                #resolve
596            }
597        }
598
599        #[automatically_derived]
600        impl #p::ConfigField for #name {
601            fn compile(
602                raw: &#p::Value,
603                cx: &mut #p::CompileCx<'_>,
604            ) -> ::core::option::Option<Self> {
605                <Self as #p::FunctionConfig>::compile(raw, cx)
606            }
607
608            fn visit(&self, #v: &mut #p::Visit<'_>) {
609                #(#visits)*
610            }
611
612            fn resolve(
613                &self,
614                r: &mut dyn #p::Resolver,
615            ) -> #p::Result<::core::option::Option<#p::OwnedDataValue>> {
616                ::core::result::Result::map(
617                    <Self as #p::FunctionConfig>::resolve(self, r),
618                    ::core::option::Option::Some,
619                )
620            }
621
622            fn kind() -> #p::FieldKind {
623                #p::FieldKind::Nested(<Self as #p::FunctionConfig>::fields)
624            }
625        }
626
627        #(#assertions)*
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::*;
634
635    /// What the derive says about `input`, which it must refuse.
636    fn refused(input: TokenStream2) -> String {
637        let input: DeriveInput = syn::parse2(input).expect("a struct");
638        expand(&input).expect_err("refused").to_string()
639    }
640
641    fn expands(input: TokenStream2) -> bool {
642        let input: DeriveInput = syn::parse2(input).expect("a struct");
643        expand(&input).is_ok()
644    }
645
646    #[test]
647    fn a_primary_output_of_another_type_is_an_expansion_error() {
648        // Marked primary.
649        assert_eq!(
650            refused(quote! {
651                struct Many {
652                    #[output(primary)]
653                    many: Vec<Output>,
654                    other: Output,
655                }
656            }),
657            "the primary output `many` must be an `Output` or an `Option<Output>`"
658        );
659        // The lone output, primary without saying so.
660        assert_eq!(
661            refused(quote! {
662                struct Keyed {
663                    #[output(default = "data.k")]
664                    r#keyed: std::collections::BTreeMap<String, Output>,
665                }
666            }),
667            "the primary output `keyed` must be an `Output` or an `Option<Output>`"
668        );
669        // An `Output`, however it is spelled, and an `Option` of one expand.
670        assert!(expands(quote! {
671            struct Spelled {
672                #[output(primary)]
673                a: ::dataflow_rs::expr::Output,
674                b: Output,
675            }
676        }));
677        assert!(expands(quote! {
678            struct Optional {
679                #[output(primary)]
680                a: core::option::Option<Output>,
681                b: Output,
682            }
683        }));
684    }
685
686    #[test]
687    fn an_option_of_an_output_counts_as_an_output() {
688        // Beside an `Output`, unmarked: two outputs, neither primary.
689        assert_eq!(
690            refused(quote! {
691                struct Two {
692                    out: Output,
693                    maybe: Option<Output>,
694                }
695            }),
696            "`Two` has 2 outputs: mark exactly one of them `#[output(primary)]`"
697        );
698        // Alone, unmarked: the primary.
699        assert!(expands(quote! {
700            struct Lone {
701                url: Expr<String>,
702                maybe: Option<Output>,
703            }
704        }));
705        // A `#[config(serde)]` field is read with serde, never an output.
706        assert!(expands(quote! {
707            struct Serde {
708                out: Output,
709                #[config(serde)]
710                maybe: Option<Output>,
711            }
712        }));
713    }
714}