trait_tactics_macros/
lib.rs

1use std::mem;
2
3use proc_macro::TokenStream;
4use quote::ToTokens;
5use syn::{
6    meta::ParseNestedMeta,
7    parse::{Nothing, Parse, ParseStream},
8    parse_macro_input, parse_quote,
9    spanned::Spanned,
10    GenericArgument, ItemImpl, Path, PathArguments, ReturnType, Type,
11};
12
13// --------------------------------------------------------------------------
14
15mod binop;
16
17#[proc_macro_attribute]
18pub fn assign_via_binop_ref_lhs(args: TokenStream, item: TokenStream) -> TokenStream {
19    binop::assign_via_binop_ref_lhs_attr(args, item)
20}
21#[proc_macro_attribute]
22pub fn assign_via_assign_ref(args: TokenStream, item: TokenStream) -> TokenStream {
23    binop::assign_via_assign_ref_attr(args, item)
24}
25#[proc_macro_attribute]
26pub fn binop_via_assign(args: TokenStream, item: TokenStream) -> TokenStream {
27    binop::binop_via_assign_attr(args, item)
28}
29#[proc_macro_attribute]
30pub fn binop_via_binop_ref_rhs(args: TokenStream, item: TokenStream) -> TokenStream {
31    binop::binop_via_binop_ref_rhs_attr(args, item)
32}
33#[proc_macro_attribute]
34pub fn binop_via_binop_ref_lhs(args: TokenStream, item: TokenStream) -> TokenStream {
35    binop::binop_via_binop_ref_lhs_attr(args, item)
36}
37
38// --------------------------------------------------------------------------
39
40#[proc_macro_attribute]
41pub fn partial_ord_via_ord(args: TokenStream, item: TokenStream) -> TokenStream {
42    parse_macro_input!(args as Nothing);
43    let ItemImplEmpty(mut item_impl) = parse_macro_input!(item);
44    item_impl.items.push(parse_quote! {
45        fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
46            ::std::option::Option::Some(::std::cmp::Ord::cmp(self, other))
47        }
48    });
49    item_impl.into_token_stream().into()
50}
51
52#[cfg(feature = "num-traits")]
53#[proc_macro_attribute]
54pub fn sum_via_fold_zero_add(args: TokenStream, item: TokenStream) -> TokenStream {
55    parse_macro_input!(args as Nothing);
56    let ItemImplEmpty(mut item_impl) = parse_macro_input!(item);
57    item_impl.items.push(parse_quote! {
58        fn sum<I: ::std::iter::Iterator<Item = Self>>(iter: I) -> Self {
59            iter.fold(::num_traits::Zero::zero(), ::std::ops::Add::add)
60        }
61    });
62    item_impl.into_token_stream().into()
63}
64
65// --------------------------------------------------------------------------
66
67fn parse_meta_value_into_arg<T: Parse>(
68    meta: &ParseNestedMeta<'_>,
69    arg: &mut Option<T>,
70) -> syn::Result<()> {
71    if arg.is_some() {
72        return Err(meta.error("conflicting argument"));
73    }
74    *arg = Some(meta.value()?.parse()?);
75    Ok(())
76}
77
78fn extract_trait_from_impl(item_impl: &ItemImpl) -> syn::Result<&Path> {
79    match &item_impl.trait_ {
80        Some((None, tr, _)) => Ok(tr),
81        Some((Some(not), _, _)) => {
82            Err(syn::Error::new(not.span, "negative implementations are not allowed"))
83        }
84        None => Err(syn::Error::new(item_impl.span(), "must be a trait implementation")),
85    }
86}
87
88/// Given a [Path] of the form `P<T>`, removes and returns the `T`.
89///
90/// If the path has no generic argument, returns the `Self` type, because most built-in
91/// binary-operator traits like [std::ops::Add] declare a default operand `<Rhs = Self>`.
92fn strip_trait_operand_type(tr: &mut Path) -> syn::Result<Type> {
93    let path_args = mem::replace(
94        &mut tr.segments.last_mut().expect("path cannot be empty").arguments,
95        PathArguments::None,
96    );
97    Ok(match path_args {
98        PathArguments::None => None,
99        PathArguments::AngleBracketed(list) => {
100            let mut it = list.args.iter();
101            if let Some(arg) = it.next() {
102                if it.next().is_some() {
103                    return Err(syn::Error::new(
104                        tr.span(),
105                        "expected exactly one generic argument",
106                    ));
107                }
108                if let GenericArgument::Type(ty) = arg {
109                    Some(ty.clone())
110                } else {
111                    return Err(syn::Error::new(arg.span(), "generic argument must be a type"));
112                }
113            } else {
114                None
115            }
116        }
117        _ => return Err(syn::Error::new(tr.span(), "generic arguments must be angle-bracketed")),
118    }
119    .unwrap_or(syn::parse_quote!(Self)))
120}
121
122trait ReturnTypeExt {
123    fn into_type(self) -> Type;
124}
125impl ReturnTypeExt for ReturnType {
126    fn into_type(self) -> Type {
127        match self {
128            ReturnType::Default => syn::parse_quote! { () },
129            ReturnType::Type(_, ty) => *ty,
130        }
131    }
132}
133
134/// An [ItemImpl] whose body must be empty.
135struct ItemImplEmpty(ItemImpl);
136impl Parse for ItemImplEmpty {
137    fn parse(input: ParseStream) -> syn::Result<Self> {
138        let item_impl = input.parse::<ItemImpl>()?;
139        if !item_impl.items.is_empty() {
140            return Err(syn::Error::new(
141                item_impl.brace_token.span.join(),
142                "impl body must be empty",
143            ));
144        }
145        Ok(Self(item_impl))
146    }
147}