1use proc_macro::TokenStream;
2use syn::{parse::Parse, parse::ParseStream, parse_macro_input, Result as SynResult, Token};
3
4struct IntoArgs {
6 target_type: syn::Type,
7 use_default: bool,
8}
9
10impl Parse for IntoArgs {
11 fn parse(input: ParseStream) -> SynResult<Self> {
12 let target_type = input.parse()?;
13 let use_default = if input.peek(Token![,]) {
14 input.parse::<Token![,]>()?;
15 input.parse::<syn::Ident>()? == "default"
16 } else {
17 false
18 };
19 Ok(IntoArgs {
20 target_type,
21 use_default,
22 })
23 }
24}
25
26#[proc_macro_attribute]
46pub fn into(args: TokenStream, input: TokenStream) -> TokenStream {
47 let args = parse_macro_input!(args as IntoArgs);
48 let target = args.target_type;
49 let use_default = args.use_default;
50 let mut input = parse_macro_input!(input as syn::ItemStruct);
51
52 let field_conversions = match &input.fields {
54 syn::Fields::Named(fields) => fields
55 .named
56 .iter()
57 .filter(|field| !is_skip(field, "into_skip"))
58 .map(|field| {
59 let name = &field.ident;
60 field
61 .attrs
62 .iter()
63 .find(|attr| attr.path().is_ident("into"))
64 .map(|attr| {
65 let value = attr.parse_args::<syn::Expr>().unwrap();
66 quote::quote!(#name: #value)
67 })
68 .unwrap_or_else(|| quote::quote!(#name: self.#name))
69 })
70 .collect::<Vec<_>>(),
71 _ => panic!("Only named fields are supported"),
72 };
73
74 match &mut input.fields {
76 syn::Fields::Named(fields) => {
77 fields.named.iter_mut().for_each(|field| {
78 field.attrs.retain(|attr| {
79 !attr.path().is_ident("into_skip") && !attr.path().is_ident("into")
80 });
81 });
82 },
83 _ => panic!("Only named fields are supported"),
84 }
85
86 let name = &input.ident;
87
88 let struct_init = if use_default {
89 quote::quote! {
90 #target {
91 #(#field_conversions,)*
92 ..Default::default()
93 }
94 }
95 } else {
96 quote::quote! {
97 #target {
98 #(#field_conversions,)*
99 }
100 }
101 };
102
103 let gen = quote::quote! {
104 #input
105
106 impl Into<#target> for #name {
107 fn into(self) -> #target {
108 #struct_init
109 }
110 }
111 };
112
113 gen.into()
114}
115
116fn is_skip(field: &syn::Field, name: &str) -> bool {
117 field.attrs.iter().any(|attr| attr.path().is_ident(name))
119}
120
121#[proc_macro_attribute]
138pub fn from(args: TokenStream, input: TokenStream) -> TokenStream {
139 let target = parse_macro_input!(args as syn::Type);
141 let mut input = parse_macro_input!(input as syn::ItemStruct);
142
143 let field_conversions = match &input.fields {
145 syn::Fields::Named(fields) => fields
146 .named
147 .iter()
148 .map(|field| {
149 let name = &field.ident;
150 field
152 .attrs
153 .iter()
154 .find(|attr| attr.path().is_ident("from"))
155 .map(|a| {
156 let value = a.parse_args::<syn::Expr>().unwrap();
157 quote::quote!(#name: #value)
158 })
159 .unwrap_or(quote::quote!(#name: source.#name))
160 })
162 .collect::<Vec<_>>(),
163 _ => panic!("Only named fields are supported"),
164 };
165
166 match &mut input.fields {
168 syn::Fields::Named(fields) => {
169 fields.named.iter_mut().for_each(|field| {
170 field.attrs.retain(|attr| !attr.path().is_ident("from"));
171 });
172 },
173 _ => panic!("Only named fields are supported"),
174 }
175
176 let name = &input.ident;
177 let gen = quote::quote! {
181 #input
182
183 impl From<#target> for #name {
184 fn from(source: #target) -> Self {
185 #name {
186 #(#field_conversions,)*
187 }
188 }
189 }
190 };
191
192 gen.into()
193}