prusti_specs/
rewriter.rs

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    /// Check whether function `item` contains a parameter called `keyword`. If
50    /// yes, return its span.
51    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![:](item.sig().output.span()),
78            ty: output_ty,
79        });
80        fn_arg
81    }
82
83    /// Turn an expression into the appropriate function
84    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        // about the span and expression chosen here:
105        // - `item_span` is set to `expr.span()` so that any errors reported
106        //   for the spec item will be reported on the span of the expression
107        //   written by the user
108        // - `let ...: bool = #expr` syntax is used to report type errors in the
109        //   expression with the correct error message, i.e. that the expected
110        //   type is `bool`, not that the expected *return* type is `bool`
111        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    /// Parse an assertion into a Rust expression
139    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    /// Parse a pledge with lhs into a Rust expression
150    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() {} // we only need this for attaching constraints to (to evaluate when the function is pure)
181        };
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    /// Parse a pledge with lhs into a Rust expression
189    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    /// Parse a loop invariant into a Rust expression
203    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    /// Parse a loop invariant into a Rust expression
223    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    /// Parse a prusti assertion into a Rust expression
232    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    /// Parse a prusti assumption into a Rust expression
241    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    /// Parse a prusti refute into a Rust expression
250    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    /// Parse a closure with specifications into a Rust expression
279    /// TODO: arguments, result (types are typically not known yet after parsing...)
280    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    /// Parse an assertion into a Rust expression
326    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}