#![allow(
dead_code,
reason = "shared macro-helper utilities; not every helper is used by every derive"
)]
pub mod pretty;
pub mod settings;
use std::ops::Deref;
use darling::FromMeta;
use syn::MetaNameValue;
#[derive(Clone, Debug)]
pub enum FieldSetting<const DEFAULT: bool, T: FromMeta> {
Settings(Settings<T>),
Flag(bool),
}
impl<const DEFAULT: bool, T: FromMeta> FieldSetting<DEFAULT, T> {
pub const fn opt_settings(&self) -> Option<&T> {
match self {
Self::Settings(settings) => Some(&settings.settings),
Self::Flag(_) => None,
}
}
#[inline]
pub fn is_enabled(&self) -> bool {
match self {
Self::Settings(settings) => settings.enabled.unwrap_or(true),
Self::Flag(value) => *value,
}
}
}
impl<const DEFAULT: bool, T: FromMeta + Default + Clone> FieldSetting<DEFAULT, T> {
#[inline]
pub fn settings(&self) -> std::borrow::Cow<'_, T> {
match self {
Self::Settings(settings) => std::borrow::Cow::Borrowed(&settings.settings),
Self::Flag(_) => std::borrow::Cow::Owned(T::default()),
}
}
}
impl<const DEFAULT: bool, T: FromMeta> FromMeta for FieldSetting<DEFAULT, T> {
fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
match item {
syn::Meta::Path(_) => Ok(Self::Flag(true)),
syn::Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(b),
..
}),
..
}) => Ok(Self::Flag(b.value)),
syn::Meta::List(list) => {
if let Ok(b) = syn::parse2::<syn::LitBool>(list.tokens.clone()) {
return Ok(Self::Flag(b.value));
}
Settings::<T>::from_meta(item).map(Self::Settings)
}
syn::Meta::NameValue(_) => Settings::<T>::from_meta(item).map(Self::Settings),
}
}
fn from_bool(value: bool) -> darling::Result<Self> {
Ok(Self::Flag(value))
}
fn from_none() -> Option<Self> {
Some(Self::Flag(DEFAULT))
}
}
impl<const DEFAULT: bool, T: FromMeta> Default for FieldSetting<DEFAULT, T> {
#[inline]
fn default() -> Self {
Self::Flag(DEFAULT)
}
}
#[derive(Clone, Debug, darling::FromMeta)]
pub struct Settings<T> {
enabled: Option<bool>,
#[darling(flatten)]
settings: T,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum BetterFlag<const DEFAULT: bool> {
Enabled,
Disabled,
#[default]
Default,
}
impl<const DEFAULT: bool> BetterFlag<DEFAULT> {
pub const fn is_enabled(self) -> bool {
match self {
Self::Enabled => true,
Self::Disabled => false,
Self::Default => DEFAULT,
}
}
pub const fn to_option(self) -> Option<bool> {
match self {
Self::Enabled => Some(true),
Self::Disabled => Some(false),
Self::Default => None,
}
}
}
impl<const DEFAULT: bool> FromMeta for BetterFlag<DEFAULT> {
fn from_none() -> Option<Self> {
Some(Self::Default)
}
fn from_value(value: &syn::Lit) -> darling::Result<Self> {
match value {
syn::Lit::Bool(b) => {
if b.value {
Ok(Self::Enabled)
} else {
Ok(Self::Disabled)
}
}
_ => Err(darling::Error::unexpected_type("expected boolean").with_span(value)),
}
}
fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
match item {
syn::Meta::Path(_) => Ok(Self::Enabled),
syn::Meta::List(metalist) => {
let lit = metalist.parse_args::<syn::Lit>()?;
Self::from_value(&lit)
}
syn::Meta::NameValue(MetaNameValue { value, .. }) => match value {
syn::Expr::Lit(expr_lit) => Self::from_value(&expr_lit.lit),
_ => Err(darling::Error::unexpected_type("expected literal").with_span(value)),
},
}
}
}
pub struct SynParse<T: syn::parse::Parse>(pub T);
impl<T: syn::parse::Parse + std::fmt::Debug> std::fmt::Debug for SynParse<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SynParse").field(&self.0).finish()
}
}
impl<T: syn::parse::Parse> Deref for SynParse<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: syn::parse::Parse> FromMeta for SynParse<T> {
fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
match item {
syn::Meta::List(list) => syn::parse2(list.tokens.clone())
.map(Self)
.map_err(|e| darling::Error::custom(e).with_span(&list.tokens)),
syn::Meta::NameValue(nv) => match &nv.value {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) => s
.parse()
.map(Self)
.map_err(|e| darling::Error::custom(e).with_span(s)),
other => Err(
darling::Error::custom("expected `key(...)` or `key = \"...\"`")
.with_span(other),
),
},
syn::Meta::Path(p) => Err(darling::Error::custom(
"expected a value, e.g. `key(...)` or `key = \"...\"`",
)
.with_span(p)),
}
}
}
#[derive(Debug, Clone)]
pub enum MaybeAloneOopsieValue<T> {
Alone,
Bool(bool),
Value(T),
}
impl<T: FromMeta> FromMeta for MaybeAloneOopsieValue<T> {
fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
match item {
syn::Meta::Path(_) => Ok(Self::Alone),
syn::Meta::List(list) => {
if let Ok(b) = syn::parse2::<syn::LitBool>(list.tokens.clone()) {
return Ok(Self::Bool(b.value));
}
let expr: syn::Expr = syn::parse2(list.tokens.clone())
.map_err(|e| darling::Error::custom(e).with_span(&list.tokens))?;
T::from_expr(&expr).map(Self::Value)
}
syn::Meta::NameValue(_) => T::from_meta(item).map(Self::Value),
}
}
}
pub fn manifest_dep_token() -> proc_macro2::TokenStream {
#[cfg(feature = "settings")]
{
settings::manifest_dep_token()
}
#[cfg(not(feature = "settings"))]
{
quote::quote! {}
}
}
pub enum SuffixDefault {
Off,
Name(String),
}
#[derive(Default)]
pub struct NamingDefaults {
pub module: Option<bool>,
pub module_suffix: Option<String>,
pub suffix: Option<SuffixDefault>,
pub vis: Option<syn::Visibility>,
}
impl NamingDefaults {
pub fn merge_over(self, base: Self) -> Self {
Self {
module: self.module.or(base.module),
module_suffix: self.module_suffix.or(base.module_suffix),
suffix: self.suffix.or(base.suffix),
vis: self.vis.or(base.vis),
}
}
}
#[derive(Default)]
pub struct TracedDefaults {
pub traced: Option<bool>,
pub location: Option<bool>,
pub timestamp: Option<bool>,
pub packed: Option<bool>,
pub boxed: Option<bool>,
pub code: Option<bool>,
}
impl TracedDefaults {
#[expect(
clippy::needless_pass_by_value,
reason = "by-value enforces a single-use merge; callers can't observe a half-merged value"
)]
pub fn merge_over(self, base: Self) -> Self {
Self {
traced: self.traced.or(base.traced),
location: self.location.or(base.location),
timestamp: self.timestamp.or(base.timestamp),
packed: self.packed.or(base.packed),
boxed: self.boxed.or(base.boxed),
code: self.code.or(base.code),
}
}
}
pub fn manifest_naming() -> (NamingDefaults, proc_macro2::TokenStream) {
#[cfg(feature = "settings")]
{
settings::naming_defaults()
}
#[cfg(not(feature = "settings"))]
{
(NamingDefaults::default(), quote::quote! {})
}
}
pub fn manifest_traced() -> (TracedDefaults, proc_macro2::TokenStream) {
#[cfg(feature = "settings")]
{
settings::traced_defaults()
}
#[cfg(not(feature = "settings"))]
{
(TracedDefaults::default(), quote::quote! {})
}
}
fn naming() -> NamingDefaults {
manifest_naming().0
}
pub fn manifest_module_default() -> Option<bool> {
naming().module
}
pub fn manifest_module_suffix() -> Option<String> {
naming().module_suffix
}
pub fn manifest_suffix_default() -> Option<SuffixDefault> {
naming().suffix
}
pub fn manifest_vis_default() -> Option<syn::Visibility> {
naming().vis
}
#[cfg(test)]
mod tests {
use syn::parse_quote;
use super::*;
#[test]
fn better_flag_is_enabled() {
assert!(BetterFlag::<true>::Enabled.is_enabled());
assert!(!BetterFlag::<true>::Disabled.is_enabled());
assert!(BetterFlag::<true>::Default.is_enabled());
assert!(BetterFlag::<false>::Enabled.is_enabled());
assert!(!BetterFlag::<false>::Disabled.is_enabled());
assert!(!BetterFlag::<false>::Default.is_enabled());
}
#[test]
fn better_flag_to_option() {
assert_eq!(BetterFlag::<true>::Enabled.to_option(), Some(true));
assert_eq!(BetterFlag::<true>::Disabled.to_option(), Some(false));
assert_eq!(BetterFlag::<true>::Default.to_option(), None);
assert_eq!(BetterFlag::<false>::Enabled.to_option(), Some(true));
assert_eq!(BetterFlag::<false>::Disabled.to_option(), Some(false));
assert_eq!(BetterFlag::<false>::Default.to_option(), None);
}
#[test]
fn better_flag_from_none() {
let flag = BetterFlag::<true>::from_none();
assert!(matches!(flag, Some(BetterFlag::Default)));
let flag = BetterFlag::<false>::from_none();
assert!(matches!(flag, Some(BetterFlag::Default)));
}
#[test]
fn better_flag_from_value_bool() {
let true_lit: syn::Lit = parse_quote!(true);
let flag = BetterFlag::<true>::from_value(&true_lit).unwrap();
assert!(matches!(flag, BetterFlag::Enabled));
let false_lit: syn::Lit = parse_quote!(false);
let flag = BetterFlag::<true>::from_value(&false_lit).unwrap();
assert!(matches!(flag, BetterFlag::Disabled));
let str_lit: syn::Lit = parse_quote!("hello");
BetterFlag::<true>::from_value(&str_lit).unwrap_err();
}
#[test]
fn better_flag_from_meta_path() {
let meta: syn::Meta = parse_quote!(my_flag);
let flag = BetterFlag::<true>::from_meta(&meta).unwrap();
assert!(matches!(flag, BetterFlag::Enabled));
}
#[test]
fn better_flag_from_meta_name_value() {
let meta: syn::Meta = parse_quote!(my_flag = true);
let flag = BetterFlag::<true>::from_meta(&meta).unwrap();
assert!(matches!(flag, BetterFlag::Enabled));
let meta: syn::Meta = parse_quote!(my_flag = false);
let flag = BetterFlag::<true>::from_meta(&meta).unwrap();
assert!(matches!(flag, BetterFlag::Disabled));
}
#[test]
fn better_flag_from_meta_list() {
let meta: syn::Meta = parse_quote!(my_flag(true));
let flag = BetterFlag::<true>::from_meta(&meta).unwrap();
assert!(matches!(flag, BetterFlag::Enabled));
let meta: syn::Meta = parse_quote!(my_flag(false));
let flag = BetterFlag::<true>::from_meta(&meta).unwrap();
assert!(matches!(flag, BetterFlag::Disabled));
}
#[derive(Debug, Clone, Default, darling::FromMeta)]
struct TestSettings {
#[darling(default)]
value: Option<String>,
}
#[test]
fn field_setting_opt_settings_returns_some_for_settings_variant() {
let setting = FieldSetting::<true, TestSettings>::Settings(Settings {
enabled: Some(true),
settings: TestSettings {
value: Some("hello".into()),
},
});
let s = setting.opt_settings();
assert!(s.is_some());
assert_eq!(s.unwrap().value.as_deref(), Some("hello"));
}
#[test]
fn field_setting_opt_settings_returns_none_for_flag_variant() {
let flag = FieldSetting::<true, TestSettings>::Flag(true);
assert!(flag.opt_settings().is_none());
}
#[test]
fn field_setting_is_enabled() {
let setting = FieldSetting::<true, TestSettings>::Settings(Settings {
enabled: Some(true),
settings: TestSettings::default(),
});
assert!(setting.is_enabled());
let setting = FieldSetting::<true, TestSettings>::Settings(Settings {
enabled: Some(false),
settings: TestSettings::default(),
});
assert!(!setting.is_enabled());
let setting = FieldSetting::<true, TestSettings>::Settings(Settings {
enabled: None,
settings: TestSettings::default(),
});
assert!(setting.is_enabled());
let flag = FieldSetting::<true, TestSettings>::Flag(true);
assert!(flag.is_enabled());
let flag = FieldSetting::<true, TestSettings>::Flag(false);
assert!(!flag.is_enabled());
}
#[test]
fn syn_parse_list_form_parses_inner_tokens() {
let meta: syn::Meta = parse_quote!(key(..=128));
let parsed = SynParse::<syn::ExprRange>::from_meta(&meta).unwrap();
assert!(parsed.start.is_none());
assert!(parsed.end.is_some());
}
#[test]
fn syn_parse_name_value_string_form_parses_string_contents() {
let meta: syn::Meta = parse_quote!(key = "..=128");
let parsed = SynParse::<syn::ExprRange>::from_meta(&meta).unwrap();
assert!(parsed.start.is_none());
assert!(parsed.end.is_some());
}
#[test]
fn syn_parse_path_form_is_rejected() {
let meta: syn::Meta = parse_quote!(key);
SynParse::<syn::ExprRange>::from_meta(&meta).unwrap_err();
}
#[test]
fn syn_parse_name_value_non_string_is_rejected() {
let meta: syn::Meta = parse_quote!(key = 42);
SynParse::<syn::ExprRange>::from_meta(&meta).unwrap_err();
}
#[test]
fn syn_parse_with_syn_path() {
let meta: syn::Meta = parse_quote!(key(crate::a::b));
let parsed = SynParse::<syn::Path>::from_meta(&meta).unwrap();
assert_eq!(parsed.segments.len(), 3);
let meta: syn::Meta = parse_quote!(key = "crate::a::b");
let parsed = SynParse::<syn::Path>::from_meta(&meta).unwrap();
assert_eq!(parsed.segments.len(), 3);
}
#[test]
fn maybe_alone_path_form_is_alone() {
let meta: syn::Meta = parse_quote!(key);
let parsed = MaybeAloneOopsieValue::<syn::Ident>::from_meta(&meta).unwrap();
assert!(matches!(parsed, MaybeAloneOopsieValue::Alone));
}
#[test]
fn maybe_alone_bool_forms() {
let meta: syn::Meta = parse_quote!(key(true));
let parsed = MaybeAloneOopsieValue::<syn::Ident>::from_meta(&meta).unwrap();
assert!(matches!(parsed, MaybeAloneOopsieValue::Bool(true)));
let meta: syn::Meta = parse_quote!(key(false));
let parsed = MaybeAloneOopsieValue::<syn::Ident>::from_meta(&meta).unwrap();
assert!(matches!(parsed, MaybeAloneOopsieValue::Bool(false)));
}
#[test]
fn maybe_alone_value_via_list_form() {
let meta: syn::Meta = parse_quote!(key(my_name));
let parsed = MaybeAloneOopsieValue::<syn::Ident>::from_meta(&meta).unwrap();
match parsed {
MaybeAloneOopsieValue::Value(ident) => assert_eq!(ident, "my_name"),
other => panic!("expected Value, got {other:?}"),
}
}
#[test]
fn maybe_alone_value_via_name_value_form_widening() {
let meta: syn::Meta = parse_quote!(key = "my_name");
let parsed = MaybeAloneOopsieValue::<syn::Ident>::from_meta(&meta).unwrap();
match parsed {
MaybeAloneOopsieValue::Value(ident) => assert_eq!(ident, "my_name"),
other => panic!("expected Value, got {other:?}"),
}
}
#[test]
fn maybe_alone_value_string_typed() {
let meta: syn::Meta = parse_quote!(key("CustomSuffix"));
let parsed = MaybeAloneOopsieValue::<String>::from_meta(&meta).unwrap();
match parsed {
MaybeAloneOopsieValue::Value(s) => assert_eq!(s, "CustomSuffix"),
other => panic!("expected Value, got {other:?}"),
}
let meta: syn::Meta = parse_quote!(key = "CustomSuffix");
let parsed = MaybeAloneOopsieValue::<String>::from_meta(&meta).unwrap();
match parsed {
MaybeAloneOopsieValue::Value(s) => assert_eq!(s, "CustomSuffix"),
other => panic!("expected Value, got {other:?}"),
}
}
}