unsafe_to_verified/
lib.rs1use 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}