unsafe_to_verified/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse, Block, Item};
4
5#[proc_macro]
6pub fn verified(input: TokenStream) -> TokenStream {
7
8
9    let input_with_braces = format!("{{ {} }}", input);
10    let input_stream = input_with_braces.parse::<TokenStream>().unwrap_or_else(|_| {
11        return syn::Error::new_spanned(
12            proc_macro2::TokenStream::from(input.clone()),
13            "Failed to process input".to_string(),
14        )
15            .to_compile_error()
16            .into();
17    });
18
19    let block = match parse::<Block>(input_stream) {
20        Ok(block) => block,
21        Err(err) => {
22            return syn::Error::new_spanned(
23                proc_macro2::TokenStream::from(input),
24                format!("Failed to parse block: {}", err),
25            )
26                .to_compile_error()
27                .into();
28        }
29    };
30
31    let expanded = quote! {
32        unsafe #block
33    };
34    TokenStream::from(expanded)
35}
36
37#[proc_macro_attribute]
38pub fn verified_item(_args: TokenStream, input: TokenStream) -> TokenStream {
39    let item = match parse::<Item>(input.clone()) {
40        Ok(item) => item,
41        Err(err) => {
42            return syn::Error::new_spanned(
43                proc_macro2::TokenStream::from(input),
44                format!("Failed to parse item: {}", err),
45            )
46                .to_compile_error()
47                .into();
48        }
49    };
50
51    match item {
52        Item::Fn(mut item_fn) => {
53            item_fn.sig.unsafety = Some(syn::token::Unsafe::default());
54            let expanded = quote! { #item_fn };
55            expanded.into()
56        }
57        Item::Impl(mut item_impl) => {
58            item_impl.unsafety = Some(syn::token::Unsafe::default());
59            let expanded = quote! { #item_impl };
60            expanded.into()
61        }
62        _ => {
63            let err = syn::Error::new_spanned(
64                item,
65                "#[verified_item] only supported on functions and impls",
66            );
67            err.to_compile_error().into()
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use proc_macro2::TokenStream as TokenStream2;
76    use quote::quote;
77    use syn::{parse_quote, Block, Item};
78
79    fn token_stream_to_string(ts: TokenStream2) -> String {
80        ts.to_string()
81    }
82
83    #[test]
84    fn test_verified_empty_block() {
85        let input: Block = parse_quote! {
86            {}
87        };
88        let expanded = quote! {
89            unsafe #input
90        };
91        let expected = quote! {
92            unsafe {}
93        };
94        assert_eq!(
95            token_stream_to_string(expanded),
96            token_stream_to_string(expected),
97            "Verified empty block should generate empty unsafe block"
98        );
99    }
100
101    #[test]
102    fn test_verified_single_statement() {
103        let input: Block = parse_quote! {
104            { let x = 42; }
105        };
106        let expanded = quote! {
107            unsafe #input
108        };
109        let expected = quote! {
110            unsafe { let x = 42; }
111        };
112        assert_eq!(
113            token_stream_to_string(expanded),
114            token_stream_to_string(expected),
115            "Verified single statement should generate unsafe block"
116        );
117    }
118
119    #[test]
120    fn test_verified_multiple_statements() {
121        let input: Block = parse_quote! {
122            {
123                let x = 42;
124                let y = x + 1;
125                println!("y = {}", y);
126            }
127        };
128        let expanded = quote! {
129            unsafe #input
130        };
131        let expected = quote! {
132            unsafe {
133                let x = 42;
134                let y = x + 1;
135                println!("y = {}", y);
136            }
137        };
138        assert_eq!(
139            token_stream_to_string(expanded),
140            token_stream_to_string(expected),
141            "Verified multiple statements should generate unsafe block"
142        );
143    }
144
145    #[test]
146    fn test_verified_unsafe_inner_block() {
147        let input: Block = parse_quote! {
148            {
149                let ptr = 0x1 as *mut i32;
150                unsafe { *ptr = 42; }
151            }
152        };
153        let expanded = quote! {
154            unsafe #input
155        };
156        let expected = quote! {
157            unsafe {
158                let ptr = 0x1 as *mut i32;
159                unsafe { *ptr = 42; }
160            }
161        };
162        assert_eq!(
163            token_stream_to_string(expanded),
164            token_stream_to_string(expected),
165            "Verified block with inner unsafe should generate correct unsafe block"
166        );
167    }
168
169    #[test]
170    fn test_verified_function() {
171        let mut item: Item = parse_quote! {
172            fn foo(x: i32) -> i32 {
173                x + 1
174            }
175        };
176        let expanded = match item {
177            Item::Fn(ref mut item_fn) => {
178                item_fn.sig.unsafety = Some(syn::token::Unsafe::default());
179                quote! { #item_fn }
180            }
181            _ => panic!("Expected Item::Fn"),
182        };
183        let expected = quote! {
184            unsafe fn foo(x: i32) -> i32 {
185                x + 1
186            }
187        };
188        assert_eq!(
189            token_stream_to_string(expanded),
190            token_stream_to_string(expected),
191            "Verified attribute should make function unsafe"
192        );
193    }
194
195    #[test]
196    fn test_verified_impl() {
197        let mut item: Item = parse_quote! {
198            impl MyType {
199                fn bar(&self) {}
200            }
201        };
202        let expanded = match item {
203            Item::Impl(ref mut item_impl) => {
204                item_impl.unsafety = Some(syn::token::Unsafe::default());
205                quote! { #item_impl }
206            }
207            _ => panic!("Expected Item::Impl"),
208        };
209        let expected = quote! {
210            unsafe impl MyType {
211                fn bar(&self) {}
212            }
213        };
214        assert_eq!(
215            token_stream_to_string(expanded),
216            token_stream_to_string(expected),
217            "Verified attribute should make impl unsafe"
218        );
219    }
220
221    #[test]
222    fn test_verified_unsupported() {
223        let item: Item = parse_quote! {
224            struct MyStruct;
225        };
226        let output = match item {
227            Item::Fn(_) | Item::Impl(_) => panic!("Expected unsupported item"),
228            item => {
229                let err = syn::Error::new_spanned(
230                    item,
231                    "#[verified_item] only supported on functions and impls",
232                );
233                err.to_compile_error()
234            }
235        };
236        let output_str = token_stream_to_string(output);
237        assert!(
238            output_str.contains("error") && output_str.contains("only supported on functions and impls"),
239            "Verified attribute on struct should produce error"
240        );
241    }
242}