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}