1use 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#[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
60struct Field<'a> {
62 ident: &'a Ident,
63 ty: &'a Type,
64 key: String,
65 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 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
139fn 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
173fn 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 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 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
321fn 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
336fn 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
359fn 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 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
390fn 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
403fn 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
415fn 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 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 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 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 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 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 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 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 assert!(expands(quote! {
700 struct Lone {
701 url: Expr<String>,
702 maybe: Option<Output>,
703 }
704 }));
705 assert!(expands(quote! {
707 struct Serde {
708 out: Output,
709 #[config(serde)]
710 maybe: Option<Output>,
711 }
712 }));
713 }
714}