summaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: 314f8138377e6b5c7ca393a476b33d8274ab535c (plain)
//! A macro for optional arguments
//!
//! There are many builder crates for Rust. Thanks to Rust's powerful macro
//! system, it is easy to make a derive macro that creates a builder for a
//! struct. But none of them take the next step of adding a macro to allow
//! using literal syntax.
//! ```
//! use feluments::*;
//!
//! #[derive(Debug, PartialEq, Eq, Builder)]
//! #[builder(vis = pub, makro)]
//! struct Foo {
//!     #[builder(default = 45)]
//!     x: i32,
//!     #[builder(into)]
//!     y: String,
//!     #[builder(optional)]
//!     z: ()
//! }
//!
//! # fn main() {
//! assert_eq!(
//!     Foo! { x: 32, y: "baz" },
//!     Foo { x: 32, y: "baz".into(), z: () },
//! );
//! # }
//! ```
//!
//! The above call to the `Foo` macro expands to the following:
//!
//! ```
//! # use feluments::*;
//! # #[derive(Debug, PartialEq, Eq, Builder)]
//! # #[builder(vis = pub)]
//! # struct Foo {
//! # #[builder(default = 45)]
//! # x: i32,
//! # #[builder(into)]
//! # y: String,
//! # #[builder(optional)]
//! # z: ()
//! # }
//! # fn main() {
//! # assert_eq!(
//! Foo::builder().x(32).y("baz").build(),
//! # Foo { x: 32, y: "baz".into(), z: () },
//! # );
//! # }
//! ```
//!
//! Although this library provides a [`Builder`] derive macro to make the
//! [`build`] macro work, the `build!` macro is also compatible with other
//! builder crates, such as [bon](https://bon-rs.com/),
//! [buildstructor](https://crates.io/crates/buildstructor), or
//! [typed-builder](https://crates.io/crates/typed-builder).

use attribute_derive::FromAttr;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{
	Expr, ExprStruct, Ident, ItemStruct, LitBool, Path, Token, Type, Visibility,
	parse::{Parse, ParseStream},
	parse_macro_input,
	token::Pub,
};

enum Default {
	None,
	Impl,
	Value(Expr),
}

struct MaybeOptionalField {
	visibility: Visibility,
	name: Ident,
	default: Default,
	auto_into: bool,
	ty: Type,
}

#[derive(FromAttr)]
#[from_attr(ident = builder)]
struct BuilderFieldOptions {
	vis: Option<Visibility>,
	optional: bool,
	default: Option<Expr>,
	into: bool,
}

#[derive(FromAttr)]
#[from_attr(ident = builder)]
struct BuilderOptions {
	vis: Option<Visibility>,
	name: Option<String>,
	makro: bool,
}

/// Creates a builder implementation for struct that is compatible with the
/// build! macro.
///
/// When the `builder` attribute is applied to the struct, it may take the
/// following arguments:
/// - `vis`: The visibility of the `builder` method.
/// - `name`: Sets the name of the builder struct.
/// - `makro`: Creates a macro to construct an instance of the struct.
///
/// When the `builder` attribute is applied to a field of the struct, there are
/// several arguments it may take:
/// - `vis`: The visibility of the setter for this field. The default is the
///   visibility of the field.
/// - `optional`: Makes the field optional. If the `default` argument is
///   provided, then the default value will be the one provided. Otherwise, it
///   will be [`Default::default`].
/// - `default`: The default value of the field. Setting this parameter also
///   implies `optional`. The value may be an expression, and it may even
///   reference previous fields.
/// - `into`: Arguments to the setter may be of any type that implements `Into`
///   for the type of the field.
///
/// # Examples
///
/// ```
/// use feluments::Builder;
///
/// #[derive(Debug, PartialEq, Eq, Builder)]
/// #[builder(vis = pub, makro)]
/// struct Foo {
///     #[builder(default = 45)]
///     x: i32,
///     #[builder(into, default = x.to_string())]
///     y: String,
///     #[builder(optional)]
///     z: ()
/// }
///
/// # fn main() {
/// assert_eq!(
///     Foo::builder().x(2).z(()).build(),
///     Foo { x: 2, y: "2".into(), z: () },
/// );
/// assert_eq!(
///     Foo! { y: "bar" },
///     Foo { x: 45, y: "bar".into(), z: () },
/// )
/// # }
/// ```
///
/// [`Default::default`]: std::default::Default::default
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive_builder(input: TokenStream) -> TokenStream {
	let structure = parse_macro_input!(input as ItemStruct);
	let struct_name = &structure.ident;
	let fields = structure
		.fields
		.into_iter()
		.map(|field| {
			let attrs = BuilderFieldOptions::from_attributes(field.attrs).unwrap();
			MaybeOptionalField {
				visibility: attrs.vis.unwrap_or(field.vis),
				name: field.ident.unwrap(),
				default: match (attrs.optional, attrs.default) {
					(true, None) => Default::Impl,
					(_, Some(expr)) => Default::Value(expr),
					(false, None) => Default::None,
				},
				auto_into: attrs.into,
				ty: field.ty,
			}
		})
		.collect::<Box<_>>();

	let builder_options = BuilderOptions::from_attributes(structure.attrs).unwrap();
	let is_builder_visible = matches!(builder_options.vis, Some(Visibility::Public(_)))
		|| fields
			.iter()
			.all(|field| matches!(field.visibility, Visibility::Public(_)));
	let builder_name = if let Some(builder_name) = builder_options.name {
		format_ident!("{builder_name}")
	} else {
		format_ident!("{}Builder", structure.ident)
	};
	let builder_macro_vis = is_builder_visible.then(|| quote! { #[macro_export]});
	let builder_macro = builder_options.makro.then(|| {
		quote! {
			#builder_macro_vis
			macro_rules! #struct_name {
				($($t: tt)*) => { ::feluments::build!(#struct_name { $($t)* })}
			}
		}
	});
	let builder_visibility = builder_options.vis.or_else(|| {
		fields
			.iter()
			.all(|field| matches!(field.visibility, Visibility::Public(_)))
			.then_some(Visibility::Public(Pub {
				span: Span::call_site(),
			}))
	});
	let field_names = fields
		.iter()
		.map(|field| field.name.clone())
		.collect::<Box<_>>();
	let field_types = fields
		.iter()
		.map(|field| field.ty.clone())
		.collect::<Box<_>>();
	let const_generics = fields
		.iter()
		.filter(|field| matches!(field.default, Default::None))
		.map(|field| Ident::new(&field.name.to_string().to_uppercase(), field.name.span()))
		.collect::<Box<_>>();
	let trues = (0..const_generics.len())
		.map(|_| LitBool::new(true, Span::call_site()))
		.collect::<Box<_>>();
	let falses = (0..const_generics.len())
		.map(|_| LitBool::new(false, Span::call_site()))
		.collect::<Box<_>>();

	let mut j = 0;
	let setters = fields
		.iter()
		.enumerate()
		.map(|(i, field)| {
            let visibility = &field.visibility;
			let setter_name = &field.name;
			let ty = &field.ty;
			let param_ty = if field.auto_into { quote! { impl ::core::convert::Into<#ty> } } else { quote! {#ty} };
			let field_name = &field.name;
			let fields_head = &field_names[0..i];
			let fields_tail = &field_names[i+1..];
			if matches!(field.default, Default::None) {
				let generics_head = &const_generics[0..j];
				let generics_tail = &const_generics[j+1..];
				j += 1;
				quote! {
					#visibility fn #setter_name(self, value: #param_ty) -> #builder_name<#(#generics_head,)* true, #(#generics_tail),*> {
						#builder_name {
							#(#fields_head: self.#fields_head,)*
							#field_name: Some(value.into()),
							#(#fields_tail: self.#fields_tail,)*
						}
					}
				}
			} else {
				let maybe_setter_name = format_ident!("maybe_{}", &field.name);
				let clear_name = format_ident!("clear_{}", &field.name);
				quote! {
					#visibility fn #setter_name(self, value: #param_ty) -> #builder_name<#(#const_generics),*> {
						#builder_name {
							#(#fields_head: self.#fields_head,)*
							#field_name: Some(value.into()),
							#(#fields_tail: self.#fields_tail,)*
						}
					}

					#visibility fn #maybe_setter_name(self, value: core::option::Option<#param_ty>) -> #builder_name<#(#const_generics),*> {
						#builder_name {
							#(#fields_head: self.#fields_head,)*
							#field_name: value.map(Into::into),
							#(#fields_tail: self.#fields_tail,)*
						}
					}

					#visibility fn #clear_name(self) -> #builder_name<#(#const_generics),*> {
						#builder_name {
							#(#fields_head: self.#fields_head,)*
							#field_name: None,
							#(#fields_tail: self.#fields_tail,)*
						}
					}
				}
			}
		})
		.collect::<Box<_>>();
	let build_field_assignments = fields.iter().map(|field| {
		let field_name = &field.name;
		match &field.default {
			Default::Value(default_value) => {
				quote! { let #field_name = self.#field_name.unwrap_or_else(|| #default_value); }
			}
			Default::Impl => quote! { let #field_name = self.#field_name.unwrap_or_default(); },
			Default::None => {
				quote! { let #field_name = unsafe { self.#field_name.unwrap_unchecked() }; }
			}
		}
	});

	quote! {
		#builder_visibility struct #builder_name<#(const #const_generics: bool),*> {
			#(#field_names: Option<#field_types>,)*
		}

		#builder_macro

		impl<#(const #const_generics: bool),*> core::default::Default for #builder_name<#(#const_generics),*> {
			fn default() -> Self {
				Self {
					#(#field_names: None,)*
				}
			}
		}

		impl #struct_name {
			#builder_visibility fn builder() -> #builder_name<#(#falses),*> {
				#builder_name::default()
			}
		}

		impl<#(const #const_generics: bool),*> #builder_name<#(#const_generics),*> {
			#(#setters)*
		}

		#[allow(all)]
		impl #builder_name<#(#trues),*> {
			#builder_visibility fn build(self) -> #struct_name {
				#(#build_field_assignments)*
				#struct_name {
					#(#field_names,)*
				}
			}
		}
	}
	.into()
}

/// Builds a value of a type that derives [`Builder`] using a constructor
/// literal syntax.
///
/// This is meant to be a convenience macro to using `Builder`, with a less
/// verbose syntax. Although this crate contains a `Builder` derive macro that
/// works well with this macro, this macro is also compatible with other crates
/// such as [bon](https://bon-rs.com/), [buildstructor](https://crates.io/crates/buildstructor),
/// or [typed-builder](https://crates.io/crates/typed-builder).
///
/// # Examples
///
/// ```
/// use feluments::*;
///
/// #[derive(Debug, PartialEq, Eq, Builder)]
/// #[builder(vis = pub)]
/// struct Foo {
///     #[builder(default = 45)]
///     x: i32,
///     #[builder(into)]
///     y: String,
///     #[builder(optional)]
///     z: ()
/// }
///
/// # fn main() {
/// assert_eq!(
///     build!(Foo { x: 32, y: "baz" }),
///     Foo { x: 32, y: "baz".into(), z: () },
/// );
/// # }
/// ```
#[proc_macro]
pub fn build(input: TokenStream) -> TokenStream {
	let structure = parse_macro_input!(input as ExprStruct);
	let struct_name = &structure.path;

	let field_names = structure.fields.iter().map(|field| &field.member);
	let field_values = structure.fields.iter().map(|field| &field.expr);

	quote! {
		#struct_name
			::builder()
			#(.#field_names(#field_values))*
			.build()
	}
	.into()
}

/// Creates a declarative macro to construct a type that derives [`Builder`]
/// using a constructor literal syntax.
///
/// This is used to create convenience macros to using `Builder`, with a less
/// verbose syntax. Although this crate contains a `Builder` derive macro that
/// works well with this macro, this macro is also compatible with other crates
/// such as [bon](https://bon-rs.com/), [buildstructor](https://crates.io/crates/buildstructor),
/// or [typed-builder](https://crates.io/crates/typed-builder).
///
/// # Examples
///
/// ```
/// use feluments::*;
///
/// #[derive(Debug, PartialEq, Eq, Builder)]
/// #[builder(vis = pub)]
/// struct Foo {
///     #[builder(default = 45)]
///     x: i32,
///     #[builder(into)]
///     y: String,
///     #[builder(optional)]
///     z: ()
/// }
///
/// // If the `pub` keyword is used, then #[macro_export] is applied
/// builder_macro!(pub Foo = Foo);
///
/// # fn main() {
/// assert_eq!(
///     build!(Foo { x: 32, y: "baz" }),
///     Foo { x: 32, y: "baz".into(), z: () },
/// );
/// # }
/// ```
#[proc_macro]
pub fn builder_macro(input: TokenStream) -> TokenStream {
	struct BuilderAssignment {
		vis: Visibility,
		left: Ident,
		_equal: Token![=],
		metavar: Option<Token![$]>,
		metacrate: Option<Token![crate]>,
		path_sep: Option<Token![::]>,
		right: Path,
	}

	impl Parse for BuilderAssignment {
		fn parse(input: ParseStream) -> syn::Result<Self> {
			let vis = input.parse()?;
			let left = input.parse()?;
			let _equal = input.parse()?;
			let metavar = input.parse()?;
			let metacrate = input.parse()?;
			let path_sep = input.parse()?;
			let right = input.parse()?;

			Ok(Self {
				vis,
				left,
				_equal,
				metavar,
				metacrate,
				path_sep,
				right,
			})
		}
	}

	let assignment = parse_macro_input!(input as BuilderAssignment);
	let attribute = match assignment.vis {
		Visibility::Public(_) => Some(quote! { #[macro_export] }),
		Visibility::Inherited => None,
		Visibility::Restricted(_) => panic!("Expected visibility to be `pub`"),
	};
	let builder_name = assignment.left;
	let BuilderAssignment {
		metavar,
		metacrate,
		path_sep,
		..
	} = assignment;
	let path = assignment.right;

	quote! {
		#attribute
		macro_rules! #builder_name {
			($($t: tt)*) => { ::feluments::build!(#metavar #metacrate #path_sep #path { $($t)* }) };
		}
	}
	.into()
}