1use crate::{
2 common::HasSignature,
3 specifications::{
4 common::{SpecificationId, SpecificationIdGenerator},
5 preparser::{parse_prusti, parse_prusti_assert_pledge, parse_prusti_pledge},
6 untyped,
7 },
8};
9use proc_macro2::{Span, TokenStream};
10use quote::{format_ident, quote, quote_spanned};
11use syn::{parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, Pat, Token, Type};
12
13pub(crate) struct AstRewriter {
14 spec_id_generator: SpecificationIdGenerator,
15}
16
17#[derive(Clone, Debug)]
18pub enum SpecItemType {
19 Precondition,
20 Postcondition,
21 Pledge,
22 Predicate(TokenStream),
23 Termination,
24}
25
26impl std::fmt::Display for SpecItemType {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 SpecItemType::Precondition => write!(f, "pre"),
30 SpecItemType::Postcondition => write!(f, "post"),
31 SpecItemType::Pledge => write!(f, "pledge"),
32 SpecItemType::Predicate(_) => write!(f, "pred"),
33 SpecItemType::Termination => write!(f, "term"),
34 }
35 }
36}
37
38impl AstRewriter {
39 pub(crate) fn new() -> Self {
40 Self {
41 spec_id_generator: SpecificationIdGenerator::new(),
42 }
43 }
44
45 pub fn generate_spec_id(&mut self) -> SpecificationId {
46 self.spec_id_generator.generate()
47 }
48
49 fn check_contains_keyword_in_params<T: HasSignature>(
52 &self,
53 item: &T,
54 keyword: &str,
55 ) -> Option<Span> {
56 for param in &item.sig().inputs {
57 if let syn::FnArg::Typed(syn::PatType { pat, .. }) = param {
58 if let syn::Pat::Ident(syn::PatIdent { ident, .. }) = &**pat {
59 if ident == keyword {
60 return Some(param.span());
61 }
62 }
63 }
64 }
65 None
66 }
67
68 fn generate_result_arg<T: HasSignature + Spanned>(&self, item: &T) -> syn::FnArg {
69 let item_span = item.span();
70 let output_ty = match &item.sig().output {
71 syn::ReturnType::Default => parse_quote_spanned!(item_span=> ()),
72 syn::ReturnType::Type(_, ty) => ty.clone(),
73 };
74 let fn_arg = syn::FnArg::Typed(syn::PatType {
75 attrs: Vec::new(),
76 pat: Box::new(parse_quote_spanned!(item_span=> result)),
77 colon_token: syn::Token.output.span()),
78 ty: output_ty,
79 });
80 fn_arg
81 }
82
83 pub fn generate_spec_item_fn<T: HasSignature + Spanned>(
85 &mut self,
86 spec_type: SpecItemType,
87 spec_id: SpecificationId,
88 expr: TokenStream,
89 item: &T,
90 ) -> syn::Result<syn::Item> {
91 if let Some(span) = self.check_contains_keyword_in_params(item, "result") {
92 return Err(syn::Error::new(
93 span,
94 "it is not allowed to use the keyword `result` as a function argument".to_string(),
95 ));
96 }
97 let item_span = expr.span();
98 let item_name = syn::Ident::new(
99 &format!("prusti_{}_item_{}_{}", spec_type, item.sig().ident, spec_id),
100 item_span,
101 );
102 let spec_id_str = spec_id.to_string();
103
104 let return_type = match &spec_type {
112 SpecItemType::Termination => quote_spanned! {item_span => Int},
113 SpecItemType::Predicate(return_type) => return_type.clone(),
114 _ => quote_spanned! {item_span => bool},
115 };
116 let mut spec_item: syn::ItemFn = parse_quote_spanned! {item_span=>
117 #[allow(unused_must_use, unused_parens, unused_variables, dead_code, non_snake_case)]
118 #[prusti::spec_only]
119 #[prusti::spec_id = #spec_id_str]
120 fn #item_name() -> #return_type {
121 let prusti_result: #return_type = #expr;
122 prusti_result
123 }
124 };
125
126 spec_item.sig.generics = item.sig().generics.clone();
127 spec_item.sig.inputs = item.sig().inputs.clone();
128 match spec_type {
129 SpecItemType::Postcondition | SpecItemType::Pledge => {
130 let fn_arg = self.generate_result_arg(item);
131 spec_item.sig.inputs.push(fn_arg);
132 }
133 _ => (),
134 }
135 Ok(syn::Item::Fn(spec_item))
136 }
137
138 pub fn process_assertion<T: HasSignature + Spanned>(
140 &mut self,
141 spec_type: SpecItemType,
142 spec_id: SpecificationId,
143 tokens: TokenStream,
144 item: &T,
145 ) -> syn::Result<syn::Item> {
146 self.generate_spec_item_fn(spec_type, spec_id, parse_prusti(tokens)?, item)
147 }
148
149 pub fn process_pledge(
151 &mut self,
152 spec_id: SpecificationId,
153 tokens: TokenStream,
154 item: &untyped::AnyFnItem,
155 ) -> syn::Result<syn::Item> {
156 self.generate_spec_item_fn(
157 SpecItemType::Pledge,
158 spec_id,
159 parse_prusti_pledge(tokens)?,
160 item,
161 )
162 }
163
164 pub fn process_pure_refinement(
165 &mut self,
166 spec_id: SpecificationId,
167 item: &untyped::AnyFnItem,
168 ) -> syn::Result<syn::Item> {
169 let item_span = item.span();
170 let item_name = syn::Ident::new(
171 &format!("prusti_pure_ghost_item_{}", item.sig().ident),
172 item_span,
173 );
174
175 let spec_id_str = spec_id.to_string();
176 let mut spec_item: syn::ItemFn = parse_quote_spanned! {item_span=>
177 #[allow(unused_must_use, unused_parens, unused_variables, dead_code)]
178 #[prusti::spec_only]
179 #[prusti::spec_id = #spec_id_str]
180 fn #item_name() {} };
182
183 spec_item.sig.generics = item.sig().generics.clone();
184 spec_item.sig.inputs = item.sig().inputs.clone();
185 Ok(syn::Item::Fn(spec_item))
186 }
187
188 pub fn process_assert_pledge(
190 &mut self,
191 spec_id_lhs: SpecificationId,
192 spec_id_rhs: SpecificationId,
193 tokens: TokenStream,
194 item: &untyped::AnyFnItem,
195 ) -> syn::Result<(syn::Item, syn::Item)> {
196 let (lhs, rhs) = parse_prusti_assert_pledge(tokens)?;
197 let lhs_item = self.generate_spec_item_fn(SpecItemType::Pledge, spec_id_lhs, lhs, item)?;
198 let rhs_item = self.generate_spec_item_fn(SpecItemType::Pledge, spec_id_rhs, rhs, item)?;
199 Ok((lhs_item, rhs_item))
200 }
201
202 pub fn process_loop_variant(
204 &mut self,
205 spec_id: SpecificationId,
206 tokens: TokenStream,
207 ) -> syn::Result<TokenStream> {
208 let expr = parse_prusti(tokens)?;
209 let spec_id_str = spec_id.to_string();
210 Ok(quote_spanned! {expr.span()=>
211 {
212 #[prusti::spec_only]
213 #[prusti::loop_body_variant_spec]
214 #[prusti::spec_id = #spec_id_str]
215 || -> Int {
216 #expr
217 };
218 }
219 })
220 }
221
222 pub fn process_loop_invariant(
224 &mut self,
225 spec_id: SpecificationId,
226 tokens: TokenStream,
227 ) -> syn::Result<TokenStream> {
228 self.process_prusti_expression(quote! {loop_body_invariant_spec}, spec_id, tokens)
229 }
230
231 pub fn process_prusti_assertion(
233 &mut self,
234 spec_id: SpecificationId,
235 tokens: TokenStream,
236 ) -> syn::Result<TokenStream> {
237 self.process_prusti_expression(quote! {prusti_assertion}, spec_id, tokens)
238 }
239
240 pub fn process_prusti_assumption(
242 &mut self,
243 spec_id: SpecificationId,
244 tokens: TokenStream,
245 ) -> syn::Result<TokenStream> {
246 self.process_prusti_expression(quote! {prusti_assumption}, spec_id, tokens)
247 }
248
249 pub fn process_prusti_refutation(
251 &mut self,
252 spec_id: SpecificationId,
253 tokens: TokenStream,
254 ) -> syn::Result<TokenStream> {
255 self.process_prusti_expression(quote! {prusti_refutation}, spec_id, tokens)
256 }
257
258 fn process_prusti_expression(
259 &mut self,
260 kind: TokenStream,
261 spec_id: SpecificationId,
262 tokens: TokenStream,
263 ) -> syn::Result<TokenStream> {
264 let expr = parse_prusti(tokens)?;
265 let spec_id_str = spec_id.to_string();
266 Ok(quote_spanned! {expr.span()=>
267 {
268 #[prusti::spec_only]
269 #[prusti::#kind]
270 #[prusti::spec_id = #spec_id_str]
271 || -> bool {
272 #expr
273 };
274 }
275 })
276 }
277
278 pub fn process_closure(
281 &mut self,
282 inputs: Punctuated<Pat, Token![,]>,
283 output: Type,
284 preconds: Vec<(SpecificationId, syn::Expr)>,
285 postconds: Vec<(SpecificationId, syn::Expr)>,
286 ) -> syn::Result<(TokenStream, TokenStream)> {
287 let process_cond =
288 |is_post: bool, id: &SpecificationId, assertion: &syn::Expr| -> TokenStream {
289 let spec_id_str = id.to_string();
290 let name = format_ident!(
291 "prusti_{}_closure_{}",
292 if is_post { "post" } else { "pre" },
293 spec_id_str
294 );
295 let callsite_span = Span::call_site();
296 let result = if is_post && !inputs.empty_or_trailing() {
297 quote_spanned! {callsite_span=> , result: #output }
298 } else if is_post {
299 quote_spanned! {callsite_span=> result: #output }
300 } else {
301 TokenStream::new()
302 };
303 quote_spanned! {callsite_span=>
304 #[prusti::spec_only]
305 #[prusti::spec_id = #spec_id_str]
306 fn #name(#inputs #result) {
307 #assertion
308 }
309 }
310 };
311
312 let mut pre_ts = TokenStream::new();
313 for (id, precond) in preconds {
314 pre_ts.extend(process_cond(false, &id, &precond));
315 }
316
317 let mut post_ts = TokenStream::new();
318 for (id, postcond) in postconds {
319 post_ts.extend(process_cond(true, &id, &postcond));
320 }
321
322 Ok((pre_ts, post_ts))
323 }
324
325 pub fn process_closure_assertion(
327 &mut self,
328 spec_id: SpecificationId,
329 tokens: TokenStream,
330 ) -> syn::Result<syn::Expr> {
331 let expr = parse_prusti(tokens)?;
332 let spec_id_str = spec_id.to_string();
333 let callsite_span = Span::call_site();
334 Ok(parse_quote_spanned! {callsite_span=>
335 #[allow(unused_must_use, unused_variables)]
336 {
337 #[prusti::spec_only]
338 #[prusti::spec_id = #spec_id_str]
339 || -> bool {
340 #expr
341 };
342 }
343 })
344 }
345}