205 lines
8.6 KiB
Rust
205 lines
8.6 KiB
Rust
//! This module is used for parsing a component function into a struct that is subsequently
|
|
//! deserialized into something useful using deserializer arguments.
|
|
//!
|
|
//! Let's break that down with a term glossary and examples which show usage and implementing.
|
|
//!
|
|
//! # Glossary
|
|
//! * `component body` - The [`ComponentBody`] struct. It's used to parse a component function [`proc_macro::TokenStream`]
|
|
//! to a reusable struct that deserializers use to modify the token stream.
|
|
//! * `deserializer` - A struct that deserializes the [`ComponentBody`] into a [`DeserializerOutput`].
|
|
//! It implements the [`DeserializerArgs`] trait, but as you can see, it's called "DeserializerArgs",
|
|
//! not "Deserializer". Why?
|
|
//! Because "args" makes more sense to the caller of [`ComponentBody::deserialize`], which
|
|
//! takes an [`DeserializerArgs`] argument. However, you can think of "DeserializerArgs" as the deserializer.
|
|
//! * `deserializer output` - A struct that implements the [`DeserializerOutput`] trait.
|
|
//! This struct is what enables deserializers to use each other, since it contains the fields that
|
|
//! a deserializer needs to turn a token stream to a different token stream.
|
|
//! This means a deserializer can get the output of another deserializer, and use that output,
|
|
//! thereby using the functionality of a different deserializer.
|
|
//! This struct also implements [`ToTokens`], which means that this is the final stage of the whole process.
|
|
//!
|
|
//! # Examples
|
|
//! *Not all imports might be included.*
|
|
//!
|
|
//! ## Usage in a procedural macro attribute
|
|
//! ```rs,ignore
|
|
//! use proc_macro::TokenStream;
|
|
//!
|
|
//! // Some documentation. You can reuse this in your deserializer structs.
|
|
//! /// This attribute changes the name of a component function to whatever the first argument is.
|
|
//! #[proc_macro_attribute]
|
|
//! pub fn name_changer(args: TokenStream, input: TokenStream) -> TokenStream {
|
|
//! // Parse the component body.
|
|
//! let component_body = parse_macro_input!(input as ComponentBody);
|
|
//!
|
|
//! // Parse the first argument, which is going to be the components new name.
|
|
//! let new_name: String = match Punctuated::<Path, Token![,]>::parse_terminated.parse(args) {
|
|
//! Err(e) => return e.to_compile_error().into(), // Convert to a compile error and return
|
|
//! Ok(args) => {
|
|
//! // If the argument exists, then convert it to a string
|
|
//! if let Some(first) = args.first() {
|
|
//! first.to_token_stream().to_string()
|
|
//! } else {
|
|
//! // If the argument doesn't exist, return an error with the appropriate message.
|
|
//! // The "span" is the location of some code.
|
|
//! // The error occurred in the "args" token stream, so point the error there.
|
|
//! return Error::new(args.span(), "No new name provided").to_compile_error().into();
|
|
//! }
|
|
//! }
|
|
//! };
|
|
//!
|
|
//! let new_name = &*new_name;
|
|
//!
|
|
//! // Deserialize the component body to an output with the given args.
|
|
//! let output = component_body.deserialize(NameChangerDeserializerArgs { new_name });
|
|
//!
|
|
//! // Error handling like before, except now you're ready to return the final value.
|
|
//! match output {
|
|
//! Err(e) => e.to_compile_error().into(),
|
|
//! Ok(output) => output.to_token_stream().into(),
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! ## Using the macro in Dioxus code:
|
|
//! ```rs
|
|
//! use your_proc_macro_library::name_changer;
|
|
//! use dioxus::prelude::*;
|
|
//!
|
|
//! #[name_changer(CoolName)]
|
|
//! pub fn LameName() -> Element {
|
|
//! render! { "I want a cool name!" }
|
|
//! }
|
|
//!
|
|
//! pub fn App() -> Element {
|
|
//! render! { CoolName {} } // Renders: "I want a cool name!"
|
|
//! }
|
|
//! ```
|
|
//! ## Implementing a component body deserializer
|
|
//! ```rs
|
|
//! use syn::{Result, ItemFn, Signature, Ident};
|
|
//! use quote::quote;
|
|
//!
|
|
//! // Create a list of arguments.
|
|
//! // If there was no args, just make it empty. The "args" struct is also the deserializer struct.
|
|
//! // For the docs, you can basically copy paste this text and replace "name_changer" with your macro path.
|
|
//! // Although unfortunately, the link does not work
|
|
//! // Just make sure that your macro is well documented.
|
|
//! /// The args and deserializing implementation for the [`name_changer`] macro.
|
|
//! #[derive(Clone)]
|
|
//! pub struct NameChangerDeserializerArgs<'a> {
|
|
//! pub new_name: &'a str,
|
|
//! }
|
|
//!
|
|
//! // Create an output struct.
|
|
//! // The ItemFn represents a modified component function.
|
|
//! // To read what fields should be here, check out the `DeserializerOutput` struct docs.
|
|
//! // For the docs, you can basically copy paste this text and replace "name_changer" with your macro path.
|
|
//! // Just make sure that your macro is well documented.
|
|
//! /// The output fields and [`ToTokens`] implementation for the [`name_changer`] macro.
|
|
//! #[derive(Clone)]
|
|
//! pub struct NameChangerDeserializerOutput {
|
|
//! pub comp_fn: ItemFn,
|
|
//! }
|
|
//!
|
|
//! // Implement `ToTokens`, which is forced by `DeserializerOutput`.
|
|
//! // This will usually be very simple like this, even for complex deserializers.
|
|
//! // That's because of the way the `DeserializerOutput` is designed.
|
|
//! impl ToTokens for NameChangerDeserializerOutput {
|
|
//! fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
//! let comp_fn = &self.comp_fn;
|
|
//!
|
|
//! tokens.append_all(quote! {
|
|
//! #comp_fn
|
|
//! });
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! impl DeserializerOutput for NameChangerDeserializerOutput {}
|
|
//!
|
|
//! // Implement `DeserializerArgs`. This is the core part of deserializers.
|
|
//! impl<'a> DeserializerArgs<NameChangerDeserializerOutput> for NameChangerDeserializerArgs<'a> {
|
|
//! fn to_output(&self, component_body: &ComponentBody) -> Result<NameChangerDeserializerOutput> {
|
|
//! let old_fn = &component_body.item_fn;
|
|
//! let old_sig = &old_fn.sig;
|
|
//!
|
|
//! // For more complex uses, you will probably use `quote::parse_quote!` in combination with
|
|
//! // creating the structs manually.
|
|
//! // However, create the structs manually if you can.
|
|
//! // It's more reliable, because you only modify a certain struct field
|
|
//! // and set the others to be the clone of the original component body.
|
|
//! // That ensures that no information will be accidentally removed.
|
|
//! let new_sig = Signature {
|
|
//! ident: Ident::new(self.new_name, old_sig.ident.span()),
|
|
//! ..old_sig.clone()
|
|
//! };
|
|
//! let new_fn = ItemFn {
|
|
//! sig: new_sig,
|
|
//! ..old_fn.clone()
|
|
//! };
|
|
//!
|
|
//! Ok(NameChangerDeserializerOutput {
|
|
//! comp_fn: new_fn
|
|
//! })
|
|
//! }
|
|
//! ```
|
|
|
|
pub mod utils;
|
|
|
|
pub use utils::DeserializerArgs;
|
|
pub use utils::DeserializerOutput;
|
|
|
|
use syn::parse::{Parse, ParseStream};
|
|
use syn::spanned::Spanned;
|
|
use syn::*;
|
|
|
|
/// General struct for parsing a component body.
|
|
/// However, because it's ambiguous, it does not implement [`ToTokens`](quote::to_tokens::ToTokens).
|
|
///
|
|
/// Refer to the [module documentation](crate::component_body) for more.
|
|
pub struct ComponentBody {
|
|
/// The component function definition. You can parse this back into a [`ComponentBody`].
|
|
/// For example, you might modify it, parse it into a [`ComponentBody`], and deserialize that
|
|
/// using some deserializer. This is how deserializers use other deserializers.
|
|
///
|
|
/// **`item_fn.sig.inputs` includes the context argument!**
|
|
/// Keep this in mind when creating deserializers, because you often might want to ignore it.
|
|
/// That might be annoying, but it would be bad design for this kind of struct to not be parsable from itself.
|
|
pub item_fn: ItemFn,
|
|
/// If the function has any arguments other than the context.
|
|
pub has_extra_args: bool,
|
|
}
|
|
|
|
impl ComponentBody {
|
|
/// Deserializes the body into the [`TOutput`] with the specific [`TArgs`].
|
|
/// Even if the args are empty, the [`TArg`] type still determines what [`TOutput`] will be generated.
|
|
pub fn deserialize<TOutput, TArgs>(&self, args: TArgs) -> Result<TOutput>
|
|
where
|
|
TOutput: DeserializerOutput,
|
|
TArgs: DeserializerArgs<TOutput>,
|
|
{
|
|
args.to_output(self)
|
|
}
|
|
}
|
|
|
|
impl Parse for ComponentBody {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
let item_fn: ItemFn = input.parse()?;
|
|
|
|
let element_type_path = "::dioxus::core::Element";
|
|
|
|
if item_fn.sig.output == ReturnType::Default {
|
|
return Err(Error::new(
|
|
item_fn.sig.output.span(),
|
|
format!("Must return a <{}>", element_type_path),
|
|
));
|
|
}
|
|
|
|
let has_extra_args = !item_fn.sig.inputs.is_empty();
|
|
|
|
Ok(Self {
|
|
item_fn,
|
|
has_extra_args,
|
|
})
|
|
}
|
|
}
|