Skip to main content

derive_ctor/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![allow(dead_code)]
4
5extern crate alloc;
6use alloc::format;
7use alloc::string::{String, ToString};
8use alloc::collections::BTreeSet as HashSet;
9
10use crate::constants::CTOR_WORD;
11#[cfg(feature = "enums")]
12use crate::enums::create_enum_token_stream;
13#[cfg(feature = "structs")]
14use crate::structs::create_struct_token_stream;
15#[cfg(feature = "unions")]
16use crate::unions::create_union_token_stream;
17
18use proc_macro::TokenStream;
19use proc_macro2::{Delimiter, Ident, Span};
20use quote::ToTokens;
21use syn::parse::discouraged::AnyDelimiter;
22use syn::parse::Parse;
23use syn::parse::ParseStream;
24use syn::{parse_macro_input, Visibility};
25use syn::spanned::Spanned;
26use syn::Attribute;
27use syn::Data;
28use syn::DeriveInput;
29use syn::Error;
30use syn::token::Pub;
31use syn::Type;
32
33
34pub(crate) mod constants;
35#[cfg(feature = "enums")]
36pub(crate) mod enums;
37#[cfg(any(feature = "enums", feature = "structs", feature = "unions"))]
38pub(crate) mod fields;
39#[cfg(feature = "structs")]
40pub(crate) mod structs;
41#[cfg(feature = "unions")]
42pub(crate) mod unions;
43
44pub(crate) struct CtorDefinition {
45    pub(crate) visibility: Visibility,
46    pub(crate) ident: Ident,
47    pub(crate) attrs: HashSet<CtorAttribute>,
48}
49
50#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
51pub(crate) enum CtorAttribute {
52    Const,
53    DefaultAll,
54    Default,
55    IntoAll,
56}
57
58impl Default for CtorDefinition {
59    fn default() -> Self {
60        Self {
61            visibility: Visibility::Public(Pub {
62                span: Span::call_site(),
63            }),
64            ident: Ident::new("new", Span::mixed_site()),
65            attrs: Default::default(),
66        }
67    }
68}
69
70#[cfg(not(feature = "enums"))]
71pub(crate) fn create_enum_token_stream(_derive_input: DeriveInput) -> TokenStream {
72    use proc_macro2::Span;
73    TokenStream::from(Error::new(Span::call_site(),
74        "\"enums\" feature must be enabled to use #[derive(ctor)] on enums.").to_compile_error())
75}
76
77#[cfg(not(feature = "structs"))]
78pub(crate) fn create_struct_token_stream(_derive_input: DeriveInput) -> TokenStream {
79    TokenStream::from(Error::new(Span::call_site(),
80        "\"structs\" feature must be enabled to use #[derive(ctor)] on structs.").to_compile_error())
81}
82
83#[cfg(not(feature = "unions"))]
84pub(crate) fn create_union_token_stream(_derive_input: DeriveInput) -> TokenStream {
85    TokenStream::from(Error::new(Span::call_site(),
86        "\"unions\" feature must be enabled to use #[derive(ctor)] on unions.").to_compile_error())
87}
88
89#[cfg(feature = "shorthand")]
90#[proc_macro_derive(ctor, attributes(ctor, cloned, default, expr, into, iter))]
91pub fn derive_ctor(input: TokenStream) -> TokenStream {
92    derive_ctor_internal(input)
93}
94
95#[cfg(not(feature = "shorthand"))]
96#[proc_macro_derive(ctor, attributes(ctor))]
97pub fn derive_ctor(input: TokenStream) -> TokenStream {
98    derive_ctor_internal(input)
99}
100
101
102fn derive_ctor_internal(input: TokenStream) -> TokenStream {
103    let derive_input = parse_macro_input!(input as DeriveInput);
104
105    match &derive_input.data {
106        Data::Struct(_) => create_struct_token_stream(derive_input),
107        Data::Enum(_) => create_enum_token_stream(derive_input),
108        Data::Union(_) => create_union_token_stream(derive_input)
109    }
110}
111
112
113
114
115pub(crate) fn try_parse_attributes_with_default<T: Parse, F: Fn() -> T>(
116    attributes: &[Attribute],
117    default: F,
118) -> Result<T, Error> {
119    for attribute in attributes {
120        if attribute.path().is_ident(CTOR_WORD) {
121            return attribute.parse_args::<T>();
122        }
123    }
124    Ok(default())
125}
126
127pub(crate) fn try_parse_attributes<T: Parse>(attributes: &[Attribute]) -> Result<Option<T>, Error> {
128    for attribute in attributes {
129        if attribute.path().is_ident(CTOR_WORD) {
130            return attribute.parse_args::<T>().map(Some);
131        }
132    }
133    Ok(None)
134}
135
136pub(crate) fn is_phantom_data(typ: &Type) -> bool {
137    for token in typ.to_token_stream() {
138        if token.to_string() == "PhantomData" {
139            return true;
140        }
141    }
142    false
143}
144
145pub(crate) fn consume_delimited<T, F>(
146    stream: ParseStream,
147    expected: Delimiter,
148    expression: F,
149) -> Result<T, Error>
150where
151    F: Fn(ParseStream) -> Result<T, Error>,
152{
153    let (delimiter, span, buffer) = stream.parse_any_delimiter()?;
154    if delimiter != expected {
155        return Err(Error::new(span.span(),
156            format!("Expected enclosing {:?}", expected),
157        ));
158    }
159    expression(&buffer)
160}
161
162pub(crate) fn adjust_keyword_ident(name: String) -> String {
163    if syn::parse_str::<Ident>(&name).is_ok() {
164        return name;
165    }
166    format!("r#{}", name)
167}
168
169#[test]
170fn test_is_phantom_data() {
171    assert!(is_phantom_data(&syn::parse_str::<Type>("PhantomData").unwrap()));
172    assert!(is_phantom_data(&syn::parse_str::<Type>("&mut PhantomData<&'static str>").unwrap()));
173    assert!(!is_phantom_data(&syn::parse_str::<Type>("i32").unwrap()));
174}
175
176#[test]
177fn test_adjust_keyword_ident() {
178    assert_eq!("abc".to_string(), adjust_keyword_ident("abc".to_string()));
179    assert_eq!("r#break".to_string(), adjust_keyword_ident("break".to_string()));
180    assert_eq!("r#fn".to_string(), adjust_keyword_ident("fn".to_string()));
181    assert_eq!("r#const".to_string(), adjust_keyword_ident("const".to_string()));
182}