1#![feature(proc_macro_diagnostic)]
2
3use proc_macro::TokenStream;
4use quote::{quote, ToTokens};
5use syn::parse::{Parse, ParseStream, Result};
6use syn::{parse_macro_input, Ident, Token, Expr};
7
8struct With {
9 fields: Vec<WithField>,
10 over: Option<Expr>,
11}
12
13struct WithField {
14 name: Ident,
15 init: Option<Expr>,
16}
17
18impl Parse for With {
19 fn parse(input: ParseStream) -> Result<Self> {
20 let mut fields = vec![];
21 let mut over: Option<Expr> = None;
22 let mut found_default = false;
23
24 while !input.is_empty() {
25 if input.peek(Token![..]) {
26 input.parse::<Token![..]>()?;
27 if !input.is_empty() {
28 over = Some(input.parse::<Expr>()?);
29 }
30 found_default = true;
31 break;
32 }
33 let name = input.parse::<Ident>()?;
34 let init: Option<Expr>;
35 if input.peek(Token![:]) {
36 input.parse::<Token![:]>()?;
37 init = Some(input.parse::<Expr>()?);
38 } else {
39 init = None;
40 }
41 fields.push(WithField { name, init });
42
43 if input.peek(Token![,]) {
44 input.parse::<Token![,]>()?;
45 } else {
46 break;
47 }
48 }
49 if !found_default {
50 if !fields.is_empty() {
51 input.parse::<Token![,]>()?;
52 }
53 input.parse::<Token![..]>()?;
54 }
55 Ok(With { fields, over })
56 }
57}
58
59#[proc_macro]
78pub fn with(input: TokenStream) -> TokenStream {
79 let With { fields, over } = parse_macro_input!(input as With);
80
81 let mut expanded_fields = proc_macro2::TokenStream::new();
82
83 for WithField { name, init } in fields {
84 if let Some(init) = init {
85 expanded_fields.extend::<proc_macro2::TokenStream>(quote! {
86 _with_obj_1.#name = #init;
87 }.try_into().unwrap());
88 } else {
89 expanded_fields.extend::<proc_macro2::TokenStream>(quote! {
90 _with_obj_1.#name = #name;
91 }.try_into().unwrap());
92 }
93 }
94
95 let expanded_over = over.map(|over| over.into_token_stream()).unwrap_or(quote! {::std::default::Default::default()});
96
97 let mut expanded = TokenStream::new();
98
99 expanded.extend::<TokenStream>(quote! {
100 {
101 let mut _with_obj_1 = #expanded_over;
102 if false {
103 _with_obj_1
104 } else {
105 #expanded_fields
106 _with_obj_1
107 }
108 }
109 }.try_into().unwrap());
110
111 expanded
112}