1
use proc_macro2::TokenStream;
2

            
3
use quote::ToTokens;
4
use quote::format_ident;
5
use quote::quote;
6
use syn::Item;
7
use syn::ItemMod;
8
use syn::parse_quote;
9

            
10
16
pub(crate) fn merc_derive_terms_impl(_attributes: TokenStream, input: TokenStream) -> TokenStream {
11
    // Parse the input tokens into a syntax tree
12
16
    let mut ast: ItemMod = syn::parse2(input.clone()).expect("merc_derive_terms can only be applied to a module");
13

            
14
16
    if let Some((_, content)) = &mut ast.content {
15
        // Generated code blocks are added to this list.
16
16
        let mut added = vec![];
17

            
18
257
        for item in content.iter_mut() {
19
82
            match item {
20
37
                Item::Struct(object) => {
21
                    // If the struct is annotated with term we process it as a term.
22
91
                    if let Some(attr) = object.attrs.iter().find(|attr| attr.meta.path().is_ident("merc_term")) {
23
                        // The #[merc_term(assertion)] annotation may name an
24
                        // assertion function. When present it must be a bare
25
                        // identifier; anything else is reported as a compile
26
                        // error rather than silently dropping the check.
27
37
                        let assertion = if attr.meta.require_list().is_ok() {
28
37
                            match attr.parse_args::<syn::Ident>() {
29
37
                                Ok(assertion) => {
30
37
                                    let assertion_msg = format!("{assertion}");
31
37
                                    quote!(
32
                                        debug_assert!(#assertion(&term), "Term {:?} does not satisfy {}", term, #assertion_msg)
33
                                    )
34
                                }
35
                                Err(error) => {
36
                                    let message =
37
                                        format!("merc_term expects a single assertion function identifier: {error}");
38
                                    quote!(compile_error!(#message))
39
                                }
40
                            }
41
                        } else {
42
                            // Bare `#[merc_term]` without arguments: no assertion.
43
                            quote!()
44
                        };
45

            
46
                        // Add the expected derive macros to the input struct.
47
37
                        object
48
37
                            .attrs
49
37
                            .push(parse_quote!(#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]));
50

            
51
                        // ALL structs in this module must contain the term.
52
37
                        assert!(
53
37
                            object.fields.iter().any(|field| {
54
37
                                if let Some(name) = &field.ident {
55
37
                                    name == "term"
56
                                } else {
57
                                    false
58
                                }
59
37
                            }),
60
                            "The struct {} in mod {} has no field 'term: ATerm'",
61
                            object.ident,
62
                            ast.ident
63
                        );
64

            
65
37
                        let name = format_ident!("{}", object.ident);
66

            
67
                        // Simply the generics from the struct.
68
37
                        let generics = object.generics.clone();
69

            
70
                        // Helper to create generics with added lifetimes.
71
148
                        fn create_generics_with_lifetimes(
72
148
                            base_generics: &syn::Generics,
73
148
                            lifetime_names: &[&str],
74
148
                        ) -> syn::Generics {
75
148
                            let mut generics = base_generics.clone();
76
185
                            for &lifetime_name in lifetime_names {
77
185
                                generics.params.push(syn::GenericParam::Lifetime(syn::LifetimeParam {
78
185
                                    attrs: vec![],
79
185
                                    lifetime: syn::Lifetime::new(lifetime_name, proc_macro2::Span::call_site()),
80
185
                                    bounds: syn::punctuated::Punctuated::new(),
81
185
                                    colon_token: None,
82
185
                                }));
83
185
                            }
84
148
                            generics
85
148
                        }
86

            
87
                        // The generics from the struct with <'a, 'b> added for the Term trait.
88
37
                        let generics_term = create_generics_with_lifetimes(&object.generics, &["'a", "'b"]);
89

            
90
                        // Only 'a prepended for the Ref<'a> struct.
91
37
                        let generics_ref = create_generics_with_lifetimes(&object.generics, &["'a"]);
92

            
93
                        // Only 'b prepended for the Ref<'b> struct.
94
37
                        let generics_ref_b = create_generics_with_lifetimes(&object.generics, &["'b"]);
95

            
96
                        // Only 'static prepended for the Ref<'static> struct.
97
37
                        let generics_static = create_generics_with_lifetimes(&object.generics, &["'static"]);
98

            
99
                        // Handle PhantomData generics - use void type if no generics exist
100
37
                        let generics_phantom = if object.generics.params.is_empty() {
101
37
                            quote!(<()>)
102
                        } else {
103
                            generics.to_token_stream()
104
                        };
105

            
106
                        // Add a <name>Ref struct that contains the ATermRef<'a> and
107
                        // the implementation and both protect and borrow. Also add
108
                        // the conversion from and to an ATerm.
109
37
                        let name_ref = format_ident!("{}Ref", object.ident);
110
37
                        let generated: TokenStream = quote!(
111
                            impl #generics #name #generics {
112
                                pub fn copy #generics_ref(&'a self) -> #name_ref #generics_ref {
113
                                    self.term.copy().into()
114
                                }
115
                            }
116

            
117
                            impl #generics From<ATerm> for #name #generics {
118
                                fn from(term: ATerm) -> #name {
119
                                    #assertion;
120
                                    #name {
121
                                        term
122
                                    }
123
                                }
124
                            }
125

            
126
                            impl #generics ::std::convert::From<#name #generics> for ATerm {
127
                                fn from(value: #name #generics) -> ATerm {
128
                                    value.term
129
                                }
130
                            }
131

            
132
                            impl #generics ::std::ops::Deref for #name #generics{
133
                                type Target = ATerm;
134

            
135
                                fn deref(&self) -> &Self::Target {
136
                                    &self.term
137
                                }
138
                            }
139

            
140
                            impl #generics ::std::borrow::Borrow<ATerm> for #name #generics{
141
                                fn borrow(&self) -> &ATerm {
142
                                    &self.term
143
                                }
144
                            }
145

            
146
                            impl #generics Markable for #name #generics{
147
                                fn mark(&self, marker: &mut Marker) {
148
                                    self.term.mark(marker);
149
                                }
150

            
151
                                fn contains_term(&self, term: &ATermRef<'_>) -> bool {
152
                                    &self.term.copy() == term
153
                                }
154

            
155
                                fn contains_symbol(&self, symbol: &SymbolRef<'_>) -> bool {
156
                                    self.get_head_symbol() == *symbol
157
                                }
158

            
159
                                fn len(&self) -> usize {
160
                                    1
161
                                }
162
                            }
163

            
164
                            impl ::std::fmt::Debug for #name #generics {
165
                                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
166
                                    write!(f, "{:?}", self.term)
167
                                }
168
                            }
169

            
170
                            impl #generics_term Term<'a, 'b> for #name #generics where 'b: 'a {
171
                                delegate! {
172
                                    to self.term {
173
                                        fn protect(&self) -> ATerm;
174
                                        fn arg(&'b self, index: usize) -> ATermRef<'a>;
175
                                        fn arguments(&'b self) -> ATermArgs<'a>;
176
                                        fn copy(&'b self) -> ATermRef<'a>;
177
                                        fn get_head_symbol(&'b self) -> SymbolRef<'a>;
178
                                        fn iter(&'b self) -> TermIterator<'a>;
179
                                        fn index(&self) -> usize;
180
                                        fn shared(&self) -> &ATermIndex;
181
                                    }
182
                                }
183
                            }
184

            
185
                            #[derive(Eq, Hash, Ord, PartialEq, PartialOrd)]
186
                            pub struct #name_ref #generics_ref {
187
                                pub(crate) term: ATermRef<'a>,
188
                                _marker: ::std::marker::PhantomData #generics_phantom,
189
                            }
190

            
191
                            impl #generics_ref  #name_ref #generics_ref  {
192
                                pub fn copy<'b>(&'b self) -> #name_ref #generics_ref_b{
193
                                    self.term.copy().into()
194
                                }
195

            
196
                                pub fn protect(&self) -> #name {
197
                                    self.term.protect().into()
198
                                }
199
                            }
200

            
201
                            impl #generics_ref ::std::convert::From<ATermRef<'a>> for #name_ref #generics_ref {
202
                                fn from(term: ATermRef<'a>) -> #name_ref #generics_ref  {
203
                                    #assertion;
204
                                    #name_ref {
205
                                        term,
206
                                        _marker: ::std::marker::PhantomData,
207
                                    }
208
                                }
209
                            }
210

            
211
                            impl #generics_ref ::std::convert::From<#name_ref #generics_ref> for ATermRef<'a> {
212
                                fn from(value: #name_ref #generics_ref) -> ATermRef<'a> {
213
                                    value.term
214
                                }
215
                            }
216

            
217
                            impl #generics_ref ::std::fmt::Debug for #name_ref #generics_ref {
218
                                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
219
                                    write!(f, "{:?}", self.term)
220
                                }
221
                            }
222

            
223
                            impl #generics_term Term<'a, '_> for #name_ref #generics_ref  {
224
                                delegate! {
225
                                    to self.term {
226
                                        fn protect(&self) -> ATerm;
227
                                        fn arg(&self, index: usize) -> ATermRef<'a>;
228
                                        fn arguments(&self) -> ATermArgs<'a>;
229
                                        fn copy(&self) -> ATermRef<'a>;
230
                                        fn get_head_symbol(&self) -> SymbolRef<'a>;
231
                                        fn iter(&self) -> TermIterator<'a>;
232
                                        fn index(&self) -> usize;
233
                                        fn shared(&self) -> &ATermIndex;
234
                                    }
235
                                }
236
                            }
237

            
238
                            impl #generics_ref ::std::borrow::Borrow<ATermRef<'a>> for #name_ref #generics_ref {
239
                                fn borrow(&self) -> &ATermRef<'a> {
240
                                    &self.term
241
                                }
242
                            }
243

            
244
                            impl #generics_ref Markable for #name_ref #generics_ref {
245
                                fn mark(&self, marker: &mut Marker) {
246
                                    self.term.mark(marker);
247
                                }
248

            
249
                                fn contains_term(&self, term: &ATermRef<'_>) -> bool {
250
                                    &self.term == term
251
                                }
252

            
253
                                fn contains_symbol(&self, symbol: &SymbolRef<'_>) -> bool {
254
                                    self.get_head_symbol() == *symbol
255
                                }
256

            
257
                                fn len(&self) -> usize {
258
                                    1
259
                                }
260
                            }
261

            
262
                            // SAFETY: `#name_ref` is a `#[repr(Rust)]` wrapper whose only
263
                            // non-zero-sized field is `ATermRef<'a>`, which is itself a
264
                            // lifetime-erasable handle into the global term pool.
265
                            unsafe impl Transmutable for #name_ref #generics_static {
266
                                type Target #generics_ref = #name_ref #generics_ref;
267

            
268
                                unsafe fn transmute_lifetime<'a>(&self) -> &'a Self::Target #generics_ref {
269
                                    // SAFETY: see the trait impl comment above.
270
                                    unsafe { ::std::mem::transmute::<&Self, &'a #name_ref #generics_ref>(self) }
271
                                }
272

            
273
                                unsafe fn transmute_lifetime_mut<'a>(&mut self) -> &'a mut Self::Target #generics_ref {
274
                                    // SAFETY: see the trait impl comment above.
275
                                    unsafe { ::std::mem::transmute::<&mut Self, &'a mut #name_ref #generics_ref>(self) }
276
                                }
277
                            }
278
                        );
279

            
280
37
                        added.push(Item::Verbatim(generated));
281
                    }
282
                }
283
82
                Item::Impl(implementation)
284
82
                    if !implementation
285
82
                        .attrs
286
82
                        .iter()
287
82
                        .any(|attr| attr.meta.path().is_ident("merc_ignore")) =>
288
                {
289
                    // Duplicate the implementation for the Ref struct that is generated above.
290
61
                    let mut ref_implementation = implementation.clone();
291

            
292
                    // Remove ignored functions
293
133
                    ref_implementation.items.retain(|item| match item {
294
133
                        syn::ImplItem::Fn(func) => {
295
247
                            !func.attrs.iter().any(|attr| attr.meta.path().is_ident("merc_ignore"))
296
                        }
297
                        _ => true,
298
133
                    });
299

            
300
                    // Only `impl Name { .. }` blocks with a bare-identifier self
301
                    // type are duplicated; generic or path-qualified self types
302
                    // (e.g. `impl<T> Name<T>` or `impl module::Name`) are not yet
303
                    // supported and are reported as a clear compile error rather
304
                    // than panicking the macro or silently dropping the block.
305
61
                    match ref_implementation.self_ty.as_ref() {
306
61
                        syn::Type::Path(path) if path.path.get_ident().is_some() => {
307
61
                            let identifier = path.path.get_ident().expect("checked by the match guard");
308
61

            
309
61
                            // Build an identifier with the postfix Ref<'_>
310
61
                            let name_ref = format_ident!("{}Ref", identifier);
311
61
                            let path: syn::Path = parse_quote!(#name_ref <'_>);
312
61

            
313
61
                            ref_implementation.self_ty = Box::new(syn::Type::Path(syn::TypePath { qself: None, path }));
314
61

            
315
61
                            added.push(Item::Verbatim(ref_implementation.into_token_stream()));
316
61
                        }
317
                        _ => {
318
                            let message = "merc_derive_terms can only duplicate impl blocks whose self type is a \
319
                                 bare identifier; generic or path-qualified self types are not yet supported. \
320
                                 Annotate the impl with #[merc_ignore] to skip it.";
321
                            added.push(Item::Verbatim(quote!(compile_error!(#message);)));
322
                        }
323
                    }
324
                }
325
159
                _ => {
326
159
                    // Ignore the rest.
327
159
                }
328
            }
329
        }
330

            
331
16
        content.append(&mut added);
332
    }
333

            
334
    // Hand the output tokens back to the compiler
335
16
    ast.into_token_stream()
336
16
}
337

            
338
#[cfg(test)]
339
mod tests {
340
    use std::str::FromStr;
341

            
342
    use proc_macro2::TokenStream;
343

            
344
    use crate::merc_derive_terms_impl;
345

            
346
    #[test]
347
1
    fn test_macro() {
348
1
        let input = "
349
1
            mod anything {
350
1

            
351
1
                #[merc_term(test)]
352
1
                struct Test {
353
1
                    term: ATerm,
354
1
                }
355
1

            
356
1
                impl Test {
357
1
                    fn a_function() {
358
1

            
359
1
                    }
360
1
                }
361
1
            }
362
1
        ";
363

            
364
1
        let tokens = TokenStream::from_str(input).unwrap();
365
1
        let result = merc_derive_terms_impl(TokenStream::default(), tokens);
366

            
367
        // The generated module must parse back as valid Rust and mention the
368
        // generated `TestRef` type.
369
1
        let rendered = result.to_string();
370
1
        syn::parse2::<syn::File>(result).expect("generated code should be valid Rust");
371
1
        assert!(
372
1
            rendered.contains("TestRef"),
373
            "expected a generated TestRef type, got: {rendered}"
374
        );
375
1
    }
376
}