-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Implement node output destructuring #3806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
2e97e81
e215927
f05970c
f81c4da
9db03a5
051b690
e5e4936
958810d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| use convert_case::{Case, Casing}; | ||
|
Keavon marked this conversation as resolved.
|
||
| use proc_macro_crate::FoundCrate; | ||
| use proc_macro2::{Span, TokenStream as TokenStream2}; | ||
| use quote::{format_ident, quote}; | ||
| use syn::{Data, DeriveInput, Error, LitStr, Meta, Type, spanned::Spanned}; | ||
|
|
||
| pub fn derive(input: DeriveInput) -> syn::Result<TokenStream2> { | ||
| let struct_name = input.ident; | ||
| let generics = input.generics; | ||
| let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); | ||
|
|
||
| let Data::Struct(data_struct) = input.data else { | ||
| return Err(Error::new(Span::call_site(), "Deriving `Destruct` is currently only supported for structs")); | ||
| }; | ||
|
|
||
| let core_types = match proc_macro_crate::crate_name("core-types") | ||
| .map_err(|e| Error::new(Span::call_site(), format!("Failed to find location of core_types. Make sure it is imported as a dependency: {e}")))? | ||
| { | ||
| FoundCrate::Itself => quote!(crate), | ||
| FoundCrate::Name(name) => { | ||
| let ident = syn::Ident::new(&name, Span::call_site()); | ||
| quote!(#ident) | ||
| } | ||
| }; | ||
|
|
||
| let mut node_implementations = Vec::with_capacity(data_struct.fields.len()); | ||
| let mut output_fields = Vec::with_capacity(data_struct.fields.len()); | ||
|
|
||
| for field in data_struct.fields { | ||
| let Some(field_name) = field.ident else { | ||
| return Err(Error::new(field.span(), "Destruct cannot be used on tuple structs")); | ||
| }; | ||
|
|
||
| let ty = field.ty; | ||
| let output_name = parse_output_name(&field.attrs)?.unwrap_or_else(|| field_name.to_string().to_case(Case::Title)); | ||
| let output_name_lit = LitStr::new(&output_name, field_name.span()); | ||
|
|
||
| let fn_name = format_ident!("extract_{}_{}", struct_name.to_string().to_case(Case::Snake), field_name); | ||
| let node_struct_name = format_ident!("{}Node", fn_name.to_string().to_case(Case::Pascal)); | ||
|
|
||
| node_implementations.push(generate_extractor_node(&core_types, &fn_name, &struct_name, &field_name, &ty, &output_name_lit)); | ||
| output_fields.push(quote! { | ||
| #core_types::registry::StructField { | ||
| name: #output_name_lit, | ||
| node_path: concat!(std::module_path!(), "::", stringify!(#node_struct_name)), | ||
| ty: #core_types::concrete!(#ty), | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| Ok(quote! { | ||
| #(#node_implementations)* | ||
|
|
||
| impl #impl_generics #core_types::registry::Destruct for #struct_name #ty_generics #where_clause { | ||
| fn fields() -> &'static [#core_types::registry::StructField] { | ||
| static FIELDS: std::sync::OnceLock<Vec<#core_types::registry::StructField>> = std::sync::OnceLock::new(); | ||
| FIELDS.get_or_init(|| vec![ | ||
| #(#output_fields,)* | ||
| ]).as_slice() | ||
|
Comment on lines
+68
to
+71
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you use heap memory + syncronization here instead of constructing a slice in place? |
||
| } | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| fn generate_extractor_node(core_types: &TokenStream2, fn_name: &syn::Ident, struct_name: &syn::Ident, field_name: &syn::Ident, ty: &Type, output_name: &LitStr) -> TokenStream2 { | ||
| quote! { | ||
| #[node_macro::node(category(""), name(#output_name))] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we add the |
||
| fn #fn_name(_: impl #core_types::Ctx, data: #struct_name) -> #ty { | ||
| data.#field_name | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn parse_output_name(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> { | ||
| let mut output_name = None; | ||
|
|
||
| for attr in attrs { | ||
| if !attr.path().is_ident("output") { | ||
| continue; | ||
| } | ||
|
|
||
| let mut this_output_name = None; | ||
| match &attr.meta { | ||
| Meta::Path(_) => { | ||
| return Err(Error::new_spanned(attr, "Expected output metadata like #[output(name = \"Result\")]")); | ||
| } | ||
| Meta::NameValue(_) => { | ||
| return Err(Error::new_spanned(attr, "Expected output metadata like #[output(name = \"Result\")]")); | ||
| } | ||
| Meta::List(_) => { | ||
| attr.parse_nested_meta(|meta| { | ||
| if meta.path.is_ident("name") { | ||
| if this_output_name.is_some() { | ||
| return Err(meta.error("Multiple output names provided for one field")); | ||
| } | ||
| let value = meta.value()?; | ||
| let lit: LitStr = value.parse()?; | ||
| this_output_name = Some(lit.value()); | ||
| Ok(()) | ||
| } else { | ||
| Err(meta.error("Unsupported output metadata. Supported syntax is #[output(name = \"...\")]")) | ||
| } | ||
| })?; | ||
| } | ||
| } | ||
|
|
||
| let this_output_name = this_output_name.ok_or_else(|| Error::new_spanned(attr, "Missing output name. Use #[output(name = \"...\")]"))?; | ||
| if output_name.is_some() { | ||
| return Err(Error::new_spanned(attr, "Multiple #[output(...)] attributes are not allowed on one field")); | ||
| } | ||
| output_name = Some(this_output_name); | ||
| } | ||
|
|
||
| Ok(output_name) | ||
| } | ||
|
Comment on lines
+90
to
+131
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this function only used for overriding the field name? I don't think this code is currently used anywhere right? If it is indeed not used, it should be removed to reduce the code complexity |
||
Uh oh!
There was an error while loading. Please reload this page.