prusti_specs/
lib.rs

1#![deny(unused_must_use)]
2#![feature(extract_if)]
3#![feature(box_patterns)]
4#![feature(proc_macro_span)]
5#![feature(if_let_guard)]
6#![feature(assert_matches)]
7// This Clippy chcek seems to be always wrong.
8#![allow(clippy::iter_with_drain)]
9#![warn(clippy::disallowed_types)]
10
11#[macro_use]
12mod common;
13mod extern_spec_rewriter;
14mod type_cond_specs;
15mod parse_closure_macro;
16mod parse_quote_spanned;
17mod predicate;
18mod rewriter;
19mod span_overrider;
20mod spec_attribute_kind;
21pub mod specifications;
22mod type_model;
23mod user_provided_type_params;
24mod print_counterexample;
25
26use proc_macro2::{Span, TokenStream, TokenTree};
27use quote::{quote, quote_spanned, ToTokens};
28use rewriter::AstRewriter;
29use std::convert::TryInto;
30use syn::{spanned::Spanned, visit::Visit};
31
32use crate::{
33    common::{merge_generics, RewritableReceiver, SelfTypeRewriter},
34    predicate::{is_predicate_macro, ParsedPredicate},
35    specifications::preparser::{parse_prusti, parse_type_cond_spec, NestedSpec},
36};
37pub use extern_spec_rewriter::ExternSpecKind;
38use parse_closure_macro::ClosureWithSpec;
39pub use spec_attribute_kind::SpecAttributeKind;
40use specifications::{common::SpecificationId, untyped};
41
42pub const SPECS_VERSION: &str = env!("CARGO_PKG_VERSION");
43
44macro_rules! handle_result {
45    ($parse_result: expr) => {
46        match $parse_result {
47            Ok(data) => data,
48            Err(err) => return err.to_compile_error(),
49        }
50    };
51}
52
53macro_rules! result_to_tokens {
54    ($body:block) => {{
55        let body = || $body;
56        handle_result!(body())
57    }};
58}
59
60fn extract_prusti_attributes(
61    item: &mut untyped::AnyFnItem,
62) -> Vec<(SpecAttributeKind, TokenStream)> {
63    let mut prusti_attributes = Vec::new();
64    let mut regular_attributes = Vec::new();
65    for attr in item.attrs_mut().drain(0..) {
66        if attr.path.segments.len() == 1
67            || (attr.path.segments.len() == 2 && attr.path.segments[0].ident == "prusti_contracts")
68        {
69            let idx = attr.path.segments.len() - 1;
70            if let Ok(attr_kind) = attr.path.segments[idx].ident.to_string().try_into() {
71                let tokens = match attr_kind {
72                    SpecAttributeKind::Requires
73                    | SpecAttributeKind::Ensures
74                    | SpecAttributeKind::AfterExpiry
75                    | SpecAttributeKind::AssertOnExpiry
76                    | SpecAttributeKind::RefineSpec => {
77                        // We need to drop the surrounding parenthesis to make the
78                        // tokens identical to the ones passed by the native procedural
79                        // macro call.
80                        let mut iter = attr.tokens.into_iter();
81                        let TokenTree::Group(group) = iter.next().unwrap() else {
82                            unreachable!()
83                        };
84                        assert!(iter.next().is_none(), "Unexpected shape of an attribute.");
85                        group.stream()
86                    }
87                    // Nothing to do for attributes without arguments.
88                    SpecAttributeKind::Pure
89                    | SpecAttributeKind::Terminates
90                    | SpecAttributeKind::Trusted
91                    | SpecAttributeKind::Predicate
92                    | SpecAttributeKind::Verified => {
93                        assert!(attr.tokens.is_empty(), "Unexpected shape of an attribute.");
94                        attr.tokens
95                    }
96                    SpecAttributeKind::Invariant => unreachable!("type invariant on function"),
97                    SpecAttributeKind::Model => unreachable!("model on function"),
98                    SpecAttributeKind::PrintCounterexample => {
99                        unreachable!("print_counterexample on function")
100                    }
101                };
102                prusti_attributes.push((attr_kind, tokens));
103            } else {
104                regular_attributes.push(attr);
105            }
106        } else {
107            regular_attributes.push(attr);
108        }
109    }
110    *item.attrs_mut() = regular_attributes;
111    prusti_attributes
112}
113
114/// Rewrite an item as required by *all* its specification attributes.
115///
116/// The first attribute (the outer one) needs to be passed via `attr_kind` and `attr` because
117/// the compiler executes it as as a procedural macro attribute.
118pub fn rewrite_prusti_attributes(
119    outer_attr_kind: SpecAttributeKind,
120    outer_attr_tokens: TokenStream,
121    item_tokens: TokenStream,
122) -> TokenStream {
123    let mut item: untyped::AnyFnItem = handle_result!(syn::parse2(item_tokens));
124
125    // Start with the outer attribute
126    let mut prusti_attributes = vec![(outer_attr_kind, outer_attr_tokens)];
127
128    // Collect the remaining Prusti attributes, removing them from `item`.
129    prusti_attributes.extend(extract_prusti_attributes(&mut item));
130
131    // make sure to also update the check in the predicate! handling method
132    if prusti_attributes
133        .iter()
134        .any(|(ak, _)| ak == &SpecAttributeKind::Predicate)
135    {
136        return syn::Error::new(
137            item.span(),
138            "`predicate!` is incompatible with other Prusti attributes",
139        )
140        .to_compile_error();
141    }
142
143    let (generated_spec_items, generated_attributes) =
144        handle_result!(generate_spec_and_assertions(prusti_attributes, &item));
145
146    quote_spanned! {item.span()=>
147        #(#generated_spec_items)*
148        #(#generated_attributes)*
149        #[prusti::specs_version = #SPECS_VERSION]
150        #item
151    }
152}
153
154type GeneratedResult = syn::Result<(Vec<syn::Item>, Vec<syn::Attribute>)>;
155
156/// Generate spec items and attributes for `item` from the Prusti attributes
157fn generate_spec_and_assertions(
158    mut prusti_attributes: Vec<(SpecAttributeKind, TokenStream)>,
159    item: &untyped::AnyFnItem,
160) -> GeneratedResult {
161    let mut generated_items = vec![];
162    let mut generated_attributes = vec![];
163
164    for (attr_kind, attr_tokens) in prusti_attributes.drain(..) {
165        let rewriting_result = match attr_kind {
166            SpecAttributeKind::Requires => generate_for_requires(attr_tokens, item),
167            SpecAttributeKind::Ensures => generate_for_ensures(attr_tokens, item),
168            SpecAttributeKind::AfterExpiry => generate_for_after_expiry(attr_tokens, item),
169            SpecAttributeKind::AssertOnExpiry => generate_for_assert_on_expiry(attr_tokens, item),
170            SpecAttributeKind::Pure => generate_for_pure(attr_tokens, item),
171            SpecAttributeKind::Verified => generate_for_verified(attr_tokens, item),
172            SpecAttributeKind::Terminates => generate_for_terminates(attr_tokens, item),
173            SpecAttributeKind::Trusted => generate_for_trusted(attr_tokens, item),
174            // Predicates are handled separately below; the entry in the SpecAttributeKind enum
175            // only exists so we successfully parse it and emit an error in
176            // `check_incompatible_attrs`; so we'll never reach here.
177            SpecAttributeKind::Predicate => unreachable!(),
178            SpecAttributeKind::Invariant => unreachable!(),
179            SpecAttributeKind::RefineSpec => type_cond_specs::generate(attr_tokens, item),
180            SpecAttributeKind::Model => unreachable!(),
181            SpecAttributeKind::PrintCounterexample => unreachable!(),
182        };
183        let (new_items, new_attributes) = rewriting_result?;
184        generated_items.extend(new_items);
185        generated_attributes.extend(new_attributes);
186    }
187
188    Ok((generated_items, generated_attributes))
189}
190
191/// Generate spec items and attributes to typecheck the and later retrieve "requires" annotations.
192fn generate_for_requires(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
193    let mut rewriter = rewriter::AstRewriter::new();
194    let spec_id = rewriter.generate_spec_id();
195    let spec_id_str = spec_id.to_string();
196    let spec_item =
197        rewriter.process_assertion(rewriter::SpecItemType::Precondition, spec_id, attr, item)?;
198    Ok((
199        vec![spec_item],
200        vec![parse_quote_spanned! {item.span()=>
201            #[prusti::pre_spec_id_ref = #spec_id_str]
202        }],
203    ))
204}
205
206/// Generate spec items and attributes to typecheck the and later retrieve "ensures" annotations.
207fn generate_for_ensures(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
208    let mut rewriter = rewriter::AstRewriter::new();
209    let spec_id = rewriter.generate_spec_id();
210    let spec_id_str = spec_id.to_string();
211    let spec_item =
212        rewriter.process_assertion(rewriter::SpecItemType::Postcondition, spec_id, attr, item)?;
213    Ok((
214        vec![spec_item],
215        vec![parse_quote_spanned! {item.span()=>
216            #[prusti::post_spec_id_ref = #spec_id_str]
217        }],
218    ))
219}
220
221/// Generate spec items and attributes to typecheck and later retrieve "after_expiry" annotations.
222fn generate_for_after_expiry(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
223    let mut rewriter = rewriter::AstRewriter::new();
224    let spec_id = rewriter.generate_spec_id();
225    let spec_id_str = spec_id.to_string();
226    let spec_item = rewriter.process_pledge(spec_id, attr, item)?;
227    Ok((
228        vec![spec_item],
229        vec![parse_quote_spanned! {item.span()=>
230            #[prusti::pledge_spec_id_ref = #spec_id_str]
231        }],
232    ))
233}
234
235/// Generate spec items and attributes to typecheck and later retrieve "after_expiry" annotations.
236fn generate_for_assert_on_expiry(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
237    let mut rewriter = rewriter::AstRewriter::new();
238    let spec_id_lhs = rewriter.generate_spec_id();
239    let spec_id_lhs_str = spec_id_lhs.to_string();
240    let spec_id_rhs = rewriter.generate_spec_id();
241    let spec_id_rhs_str = spec_id_rhs.to_string();
242    let (spec_item_lhs, spec_item_rhs) =
243        rewriter.process_assert_pledge(spec_id_lhs, spec_id_rhs, attr, item)?;
244    Ok((
245        vec![spec_item_lhs, spec_item_rhs],
246        vec![
247            parse_quote_spanned! {item.span()=>
248                #[prusti::assert_pledge_spec_id_ref_lhs = #spec_id_lhs_str]
249            },
250            parse_quote_spanned! {item.span()=>
251                #[prusti::assert_pledge_spec_id_ref_rhs = #spec_id_rhs_str]
252            },
253        ],
254    ))
255}
256
257/// Generate spec items and attributes to typecheck and later retrieve "terminates" annotations.
258fn generate_for_terminates(mut attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
259    if attr.is_empty() {
260        attr = quote! { Int::new(1) };
261    } else {
262        let mut attr_iter = attr.clone().into_iter();
263        let first = attr_iter.next();
264        if let Some(TokenTree::Ident(ident)) = first {
265            if attr_iter.next().is_none() && ident == "trusted" {
266                attr = quote! { prusti_terminates_trusted() }
267            }
268        }
269    }
270
271    let mut rewriter = rewriter::AstRewriter::new();
272    let spec_id = rewriter.generate_spec_id();
273    let spec_id_str = spec_id.to_string();
274    let spec_item =
275        rewriter.process_assertion(rewriter::SpecItemType::Termination, spec_id, attr, item)?;
276
277    Ok((
278        vec![spec_item],
279        vec![parse_quote_spanned! {item.span()=>
280            #[prusti::terminates_spec_id_ref = #spec_id_str]
281        }],
282    ))
283}
284
285/// Generate spec items and attributes to typecheck and later retrieve "pure" annotations.
286fn generate_for_pure(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
287    if !attr.is_empty() {
288        return Err(syn::Error::new(
289            attr.span(),
290            "the `#[pure]` attribute does not take parameters",
291        ));
292    }
293
294    Ok((
295        vec![],
296        vec![parse_quote_spanned! {item.span()=>
297            #[prusti::pure]
298        }],
299    ))
300}
301
302/// Generate spec items and attributes to typecheck and later retrieve "verified" annotations.
303fn generate_for_verified(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
304    if !attr.is_empty() {
305        return Err(syn::Error::new(
306            attr.span(),
307            "the `#[verified]` attribute does not take parameters",
308        ));
309    }
310
311    Ok((
312        vec![],
313        vec![parse_quote_spanned! {item.span()=>
314            #[prusti::verified]
315        }],
316    ))
317}
318
319/// Generate spec items and attributes to typecheck and later retrieve "pure" annotations, but encoded as a referenced separate function that type-conditional spec refinements can apply trait bounds to.
320fn generate_for_pure_refinements(item: &untyped::AnyFnItem) -> GeneratedResult {
321    let mut rewriter = rewriter::AstRewriter::new();
322    let spec_id = rewriter.generate_spec_id();
323    let spec_id_str = spec_id.to_string();
324    let spec_item = rewriter.process_pure_refinement(spec_id, item)?;
325
326    Ok((
327        vec![spec_item],
328        vec![parse_quote_spanned! {item.span()=>
329            #[prusti::pure_spec_id_ref = #spec_id_str]
330        }],
331    ))
332}
333
334/// Generate spec items and attributes to typecheck and later retrieve "trusted" annotations.
335fn generate_for_trusted(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult {
336    if !attr.is_empty() {
337        return Err(syn::Error::new(
338            attr.span(),
339            "the `#[trusted]` attribute does not take parameters",
340        ));
341    }
342
343    Ok((
344        vec![],
345        vec![parse_quote_spanned! {item.span()=>
346            #[prusti::trusted]
347        }],
348    ))
349}
350
351/// Generate spec items and attributes to typecheck and later retrieve "trusted" annotations.
352fn generate_for_trusted_for_types(attr: TokenStream, item: &syn::DeriveInput) -> GeneratedResult {
353    if !attr.is_empty() {
354        return Err(syn::Error::new(
355            attr.span(),
356            "the `#[trusted]` attribute does not take parameters",
357        ));
358    }
359    // TODO: reduce duplication with `invariant`
360    let mut rewriter = rewriter::AstRewriter::new();
361    let spec_id = rewriter.generate_spec_id();
362    let spec_id_str = spec_id.to_string();
363
364    let item_span = item.span();
365    let item_ident = item.ident.clone();
366    let item_name = syn::Ident::new(
367        &format!("prusti_trusted_item_{item_ident}_{spec_id}"),
368        item_span,
369    );
370
371    let spec_item: syn::ItemFn = parse_quote_spanned! {item_span=>
372        #[allow(unused_variables, dead_code, non_snake_case)]
373        #[prusti::spec_only]
374        #[prusti::trusted_type]
375        #[prusti::spec_id = #spec_id_str]
376        fn #item_name(self) {}
377    };
378
379    let generics = &item.generics;
380    let generics_idents = generics
381        .params
382        .iter()
383        .map(|generic_param| match generic_param {
384            syn::GenericParam::Type(param) => syn::GenericParam::Type(syn::TypeParam {
385                attrs: Vec::new(),
386                bounds: syn::punctuated::Punctuated::new(),
387                colon_token: None,
388                default: None,
389                eq_token: None,
390                ident: param.ident.clone(),
391            }),
392            syn::GenericParam::Lifetime(param) => syn::GenericParam::Lifetime(syn::LifetimeDef {
393                attrs: Vec::new(),
394                bounds: syn::punctuated::Punctuated::new(),
395                colon_token: None,
396                lifetime: param.lifetime.clone(),
397            }),
398            syn::GenericParam::Const(param) => syn::GenericParam::Const(syn::ConstParam {
399                attrs: Vec::new(),
400                colon_token: param.colon_token,
401                const_token: param.const_token,
402                default: None,
403                eq_token: None,
404                ident: param.ident.clone(),
405                ty: param.ty.clone(),
406            }),
407        })
408        .collect::<syn::punctuated::Punctuated<_, syn::Token![,]>>();
409    // TODO: similarly to extern_specs, don't generate an actual impl
410    let item_impl: syn::ItemImpl = parse_quote_spanned! {item_span=>
411        impl #generics #item_ident <#generics_idents> {
412            #spec_item
413        }
414    };
415
416    Ok((vec![syn::Item::Impl(item_impl)], vec![]))
417}
418
419pub fn body_variant(tokens: TokenStream) -> TokenStream {
420    generate_expression_closure(&AstRewriter::process_loop_variant, tokens)
421}
422
423pub fn body_invariant(tokens: TokenStream) -> TokenStream {
424    generate_expression_closure(&AstRewriter::process_loop_invariant, tokens)
425}
426
427pub fn prusti_assertion(tokens: TokenStream) -> TokenStream {
428    generate_expression_closure(&AstRewriter::process_prusti_assertion, tokens)
429}
430
431pub fn prusti_assume(tokens: TokenStream) -> TokenStream {
432    generate_expression_closure(&AstRewriter::process_prusti_assumption, tokens)
433}
434
435pub fn prusti_refutation(tokens: TokenStream) -> TokenStream {
436    generate_expression_closure(&AstRewriter::process_prusti_refutation, tokens)
437}
438
439/// Generates the TokenStream encoding an expression using prusti syntax
440/// Used for body invariants, assertions, and assumptions
441fn generate_expression_closure(
442    fun: &dyn Fn(&mut AstRewriter, SpecificationId, TokenStream) -> syn::Result<TokenStream>,
443    tokens: TokenStream,
444) -> TokenStream {
445    let mut rewriter = rewriter::AstRewriter::new();
446    let spec_id = rewriter.generate_spec_id();
447    let closure = handle_result!(fun(&mut rewriter, spec_id, tokens));
448    let callsite_span = Span::call_site();
449    quote_spanned! {callsite_span=>
450        #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)]
451        #[prusti::specs_version = #SPECS_VERSION]
452        if false {
453            #closure
454        }
455    }
456}
457
458pub fn closure(tokens: TokenStream) -> TokenStream {
459    let cl_spec: ClosureWithSpec = handle_result!(syn::parse(tokens.into()));
460    let callsite_span = Span::call_site();
461
462    let mut rewriter = rewriter::AstRewriter::new();
463
464    let mut preconds: Vec<(SpecificationId, syn::Expr)> = vec![];
465    let mut postconds: Vec<(SpecificationId, syn::Expr)> = vec![];
466
467    let mut cl_annotations = TokenStream::new();
468
469    for r in cl_spec.pres {
470        let spec_id = rewriter.generate_spec_id();
471        let precond =
472            handle_result!(rewriter.process_closure_assertion(spec_id, r.to_token_stream(),));
473        preconds.push((spec_id, precond));
474        let spec_id_str = spec_id.to_string();
475        cl_annotations.extend(quote_spanned! {callsite_span=>
476            #[prusti::pre_spec_id_ref = #spec_id_str]
477        });
478    }
479
480    for e in cl_spec.posts {
481        let spec_id = rewriter.generate_spec_id();
482        let postcond =
483            handle_result!(rewriter.process_closure_assertion(spec_id, e.to_token_stream(),));
484        postconds.push((spec_id, postcond));
485        let spec_id_str = spec_id.to_string();
486        cl_annotations.extend(quote_spanned! {callsite_span=>
487            #[prusti::post_spec_id_ref = #spec_id_str]
488        });
489    }
490
491    let syn::ExprClosure {
492        attrs,
493        asyncness,
494        movability,
495        capture,
496        or1_token,
497        inputs,
498        or2_token,
499        output,
500        body,
501    } = cl_spec.cl;
502
503    let output_type: syn::Type = match output {
504        syn::ReturnType::Default => {
505            return syn::Error::new(output.span(), "closure must specify return type")
506                .to_compile_error();
507        }
508        syn::ReturnType::Type(_, ref ty) => (**ty).clone(),
509    };
510
511    let (spec_toks_pre, spec_toks_post) =
512        handle_result!(rewriter.process_closure(inputs.clone(), output_type, preconds, postconds,));
513
514    let mut attrs_ts = TokenStream::new();
515    for a in attrs {
516        attrs_ts.extend(a.into_token_stream());
517    }
518
519    quote_spanned! {callsite_span=>
520        {
521            #[allow(unused_variables, unused_braces, unused_parens)]
522            #[prusti::closure]
523            #[prusti::specs_version = #SPECS_VERSION]
524            #cl_annotations #attrs_ts
525            let _prusti_closure =
526                #asyncness #movability #capture
527                #or1_token #inputs #or2_token #output
528                {
529                    #[allow(unused_must_use, unused_braces, unused_parens)]
530                    if false {
531                        #spec_toks_pre
532                    }
533                    let result = #body ;
534                    #[allow(unused_must_use, unused_braces, unused_parens)]
535                    if false {
536                        #spec_toks_post
537                    }
538                    result
539                };
540            _prusti_closure
541        }
542    }
543}
544
545pub fn refine_trait_spec(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
546    let mut impl_block: syn::ItemImpl = handle_result!(syn::parse2(tokens));
547    let impl_generics = &impl_block.generics;
548
549    let trait_path: syn::TypePath = match &impl_block.trait_ {
550        Some((_, trait_path, _)) => parse_quote_spanned!(trait_path.span()=>#trait_path),
551        None => handle_result!(Err(syn::Error::new(
552            impl_block.span(),
553            "Can refine trait specifications only on trait implementation blocks"
554        ))),
555    };
556
557    let self_type: &syn::Type = &impl_block.self_ty;
558
559    let mut new_items = Vec::new();
560    let mut generated_spec_items = Vec::new();
561    for item in impl_block.items {
562        match item {
563            syn::ImplItem::Method(method) => {
564                let mut method_item = untyped::AnyFnItem::ImplMethod(method);
565                let prusti_attributes: Vec<_> = extract_prusti_attributes(&mut method_item);
566
567                let illegal_attribute_span = prusti_attributes
568                    .iter()
569                    .filter(|(kind, _)| kind == &SpecAttributeKind::RefineSpec)
570                    .map(|(_, tokens)| tokens.span())
571                    .next();
572                if let Some(span) = illegal_attribute_span {
573                    let err = Err(syn::Error::new(
574                        span,
575                        "Type-conditional spec refinements in trait spec refinements not supported",
576                    ));
577                    handle_result!(err);
578                }
579
580                let (spec_items, generated_attributes) = handle_result!(
581                    generate_spec_and_assertions(prusti_attributes, &method_item)
582                );
583
584                spec_items
585                    .into_iter()
586                    .map(|spec_item| match spec_item {
587                        syn::Item::Fn(spec_item_fn) => spec_item_fn,
588                        x => unimplemented!("Unexpected variant: {:?}", x),
589                    })
590                    .for_each(|spec_item_fn| generated_spec_items.push(spec_item_fn));
591
592                let new_item = parse_quote_spanned! {method_item.span()=>
593                    #(#generated_attributes)*
594                    #method_item
595                };
596                new_items.push(new_item);
597            }
598            syn::ImplItem::Macro(makro) if is_predicate_macro(&makro) => {
599                let parsed_predicate =
600                    handle_result!(predicate::parse_predicate_in_impl(makro.mac.tokens.clone()));
601
602                let ParsedPredicate::Impl(predicate) = parsed_predicate else {
603                    unreachable!()
604                };
605
606                // Patch spec function: Rewrite self with _self: <SpecStruct>
607                let syn::Item::Fn(spec_function) = predicate.spec_function else {
608                    unreachable!()
609                };
610                generated_spec_items.push(spec_function);
611
612                // Add patched predicate function to new items
613                new_items.push(syn::ImplItem::Method(predicate.patched_function));
614            }
615            _ => new_items.push(item),
616        }
617    }
618
619    // Patch the spec items (merge generics, handle associated types, rewrite receiver)
620    for generated_spec_item in generated_spec_items.iter_mut() {
621        merge_generics(&mut generated_spec_item.sig.generics, impl_generics);
622        generated_spec_item.rewrite_self_type(self_type, Some(&trait_path));
623        generated_spec_item.rewrite_receiver(self_type);
624    }
625
626    impl_block.items = new_items;
627    quote_spanned! {impl_block.span()=>
628        #(#generated_spec_items)*
629        #[prusti::specs_version = #SPECS_VERSION]
630        #impl_block
631    }
632}
633
634pub fn trusted(attr: TokenStream, tokens: TokenStream) -> TokenStream {
635    if !attr.is_empty() {
636        return syn::Error::new(
637            attr.span(),
638            "the `#[trusted]` attribute does not take parameters",
639        )
640        .to_compile_error();
641    }
642
643    // `#[trusted]` can be applied to both types and to methods, figure out
644    // which one by trying to parse a `DeriveInput`.
645    if syn::parse2::<syn::DeriveInput>(tokens.clone()).is_ok() {
646        // TODO: reduce duplication with `invariant`
647        let mut rewriter = rewriter::AstRewriter::new();
648        let spec_id = rewriter.generate_spec_id();
649        let spec_id_str = spec_id.to_string();
650
651        let item: syn::DeriveInput = handle_result!(syn::parse2(tokens));
652        let item_span = item.span();
653
654        // clippy false positive (https://github.com/rust-lang/rust-clippy/issues/10577)
655        #[allow(clippy::redundant_clone)]
656        let item_ident = item.ident.clone();
657
658        let item_name = syn::Ident::new(
659            &format!("prusti_trusted_item_{item_ident}_{spec_id}"),
660            item_span,
661        );
662
663        let spec_item: syn::ItemFn = parse_quote_spanned! {item_span=>
664            #[allow(unused_variables, dead_code, non_snake_case)]
665            #[prusti::spec_only]
666            #[prusti::trusted_type]
667            #[prusti::spec_id = #spec_id_str]
668            fn #item_name(self) {}
669        };
670
671        let generics = &item.generics;
672        let generics_idents = generics
673            .params
674            .iter()
675            .map(|generic_param| match generic_param {
676                syn::GenericParam::Type(param) => syn::GenericParam::Type(syn::TypeParam {
677                    attrs: Vec::new(),
678                    bounds: syn::punctuated::Punctuated::new(),
679                    colon_token: None,
680                    default: None,
681                    eq_token: None,
682                    ident: param.ident.clone(),
683                }),
684                syn::GenericParam::Lifetime(param) => {
685                    syn::GenericParam::Lifetime(syn::LifetimeDef {
686                        attrs: Vec::new(),
687                        bounds: syn::punctuated::Punctuated::new(),
688                        colon_token: None,
689                        lifetime: param.lifetime.clone(),
690                    })
691                }
692                syn::GenericParam::Const(param) => syn::GenericParam::Const(syn::ConstParam {
693                    attrs: Vec::new(),
694                    colon_token: param.colon_token,
695                    const_token: param.const_token,
696                    default: None,
697                    eq_token: None,
698                    ident: param.ident.clone(),
699                    ty: param.ty.clone(),
700                }),
701            })
702            .collect::<syn::punctuated::Punctuated<_, syn::Token![,]>>();
703        // TODO: similarly to extern_specs, don't generate an actual impl
704        let item_impl: syn::ItemImpl = parse_quote_spanned! {item_span=>
705            impl #generics #item_ident <#generics_idents> {
706                #spec_item
707            }
708        };
709        quote_spanned! { item_span =>
710            #[prusti::specs_version = #SPECS_VERSION]
711            #item
712            #item_impl
713        }
714    } else {
715        rewrite_prusti_attributes(SpecAttributeKind::Trusted, attr, tokens)
716    }
717}
718
719pub fn invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream {
720    let mut rewriter = rewriter::AstRewriter::new();
721    let spec_id = rewriter.generate_spec_id();
722    let spec_id_str = spec_id.to_string();
723
724    let item: syn::DeriveInput = handle_result!(syn::parse2(tokens));
725    let item_span = item.span();
726
727    // clippy false positive (https://github.com/rust-lang/rust-clippy/issues/10577)
728    #[allow(clippy::redundant_clone)]
729    let item_ident = item.ident.clone();
730
731    let item_name = syn::Ident::new(
732        &format!("prusti_invariant_item_{item_ident}_{spec_id}"),
733        item_span,
734    );
735
736    let attr = handle_result!(parse_prusti(attr));
737
738    // TODO: move some of this to AstRewriter?
739    // see AstRewriter::generate_spec_item_fn for explanation of syntax below
740    let spec_item: syn::ItemFn = parse_quote_spanned! {item_span=>
741        #[allow(unused_must_use, unused_parens, unused_variables, dead_code, non_snake_case)]
742        #[prusti::spec_only]
743        #[prusti::type_invariant_spec]
744        #[prusti::spec_id = #spec_id_str]
745        fn #item_name(self) -> bool {
746            !!(#attr)
747        }
748    };
749
750    // clippy false positive (https://github.com/rust-lang/rust-clippy/issues/10577)
751    #[allow(clippy::redundant_clone)]
752    let generics = item.generics.clone();
753
754    let generics_idents = generics
755        .params
756        .iter()
757        .filter_map(|generic_param| match generic_param {
758            syn::GenericParam::Type(type_param) => Some(type_param.ident.clone()),
759            _ => None,
760        })
761        .collect::<syn::punctuated::Punctuated<_, syn::Token![,]>>();
762    // TODO: similarly to extern_specs, don't generate an actual impl
763    let item_impl: syn::ItemImpl = parse_quote_spanned! {item_span=>
764        impl #generics #item_ident < #generics_idents > {
765            #spec_item
766        }
767    };
768    quote_spanned! { item_span =>
769        #[prusti::specs_version = #SPECS_VERSION]
770        #item
771        #item_impl
772    }
773}
774
775pub fn extern_spec(attr: TokenStream, tokens: TokenStream) -> TokenStream {
776    result_to_tokens!({
777        let item: syn::Item = syn::parse2(tokens)?;
778        let mod_path: syn::Path = Some(attr)
779            .filter(|attr| !attr.is_empty())
780            .map(syn::parse2)
781            .transpose()?
782            .unwrap_or_else(|| syn::Path {
783                leading_colon: None,
784                segments: syn::punctuated::Punctuated::new(),
785            });
786        match item {
787            syn::Item::Impl(item_impl) => {
788                if !mod_path.segments.is_empty() {
789                    return Err(syn::Error::new(
790                        mod_path.span(),
791                        "extern_spec does not take a path argument for impls--you can qualify the involved types directly",
792                    ));
793                }
794                extern_spec_rewriter::impls::rewrite_extern_spec(&item_impl)
795            }
796            syn::Item::Trait(item_trait) => {
797                extern_spec_rewriter::traits::rewrite_extern_spec(&item_trait, mod_path)
798            }
799            syn::Item::Mod(item_mod) => {
800                extern_spec_rewriter::mods::rewrite_mod(&item_mod, mod_path)
801            }
802            syn::Item::ForeignMod(item_foreign_mod) => {
803                extern_spec_rewriter::foreign_mods::rewrite_extern_spec(
804                    &item_foreign_mod,
805                    &mod_path,
806                )
807            }
808            // we're expecting function stubs, so they aren't represented as Item::Fn
809            syn::Item::Verbatim(stub_tokens) => {
810                extern_spec_rewriter::functions::rewrite_stub(&stub_tokens, &mod_path, false)
811            }
812            _ => Err(syn::Error::new(
813                Span::call_site(), // this covers the entire macro invocation, unlike attr.span() which changes to only cover arguments if possible
814                "Extern specs cannot be attached to this item",
815            )),
816        }
817    })
818}
819
820pub fn predicate(tokens: TokenStream) -> TokenStream {
821    let parsed = handle_result!(predicate::parse_predicate(tokens));
822    parsed.into_token_stream()
823}
824
825pub fn rewrite_prusti_attributes_for_types(
826    outer_attr_kind: SpecAttributeKind,
827    outer_attr_tokens: TokenStream,
828    item_tokens: TokenStream,
829) -> TokenStream {
830    let mut item: syn::DeriveInput = handle_result!(syn::parse2(item_tokens));
831
832    // Start with the outer attribute
833    let mut prusti_attributes = vec![(outer_attr_kind, outer_attr_tokens)];
834
835    // Collect the remaining Prusti attributes, removing them from `item`.
836    prusti_attributes.extend(extract_prusti_attributes_for_types(&mut item));
837
838    if prusti_attributes.len() > 1
839        && prusti_attributes
840            .iter()
841            .any(|(ak, _)| ak == &SpecAttributeKind::Trusted)
842    {
843        return syn::Error::new(
844            item.span(),
845            "`trusted!` is incompatible with other Prusti attributes",
846        )
847        .to_compile_error();
848    }
849
850    // we order the attributes to ensure a model attribute is processed first
851    prusti_attributes.sort_by(|(ak1, _), (ak2, _)| ak1.cmp(ak2));
852
853    let (generated_spec_items, generated_attributes) = handle_result!(
854        generate_spec_and_assertions_for_types(prusti_attributes, &mut item)
855    );
856
857    quote_spanned! {item.span()=>
858        #(#generated_attributes)*
859        #item
860        #(#generated_spec_items)*
861    }
862}
863
864fn extract_prusti_attributes_for_types(
865    item: &mut syn::DeriveInput,
866) -> Vec<(SpecAttributeKind, TokenStream)> {
867    let mut prusti_attributes = Vec::new();
868    let mut regular_attributes = Vec::new();
869    for attr in item.attrs.drain(0..) {
870        if attr.path.segments.len() == 1 {
871            if let Ok(attr_kind) = attr.path.segments[0].ident.to_string().try_into() {
872                let tokens = match attr_kind {
873                    SpecAttributeKind::Requires => unreachable!("requires on type"),
874                    SpecAttributeKind::Ensures => unreachable!("ensures on type"),
875                    SpecAttributeKind::AfterExpiry => unreachable!("after_expiry on type"),
876                    SpecAttributeKind::AssertOnExpiry => unreachable!("assert_on_expiry on type"),
877                    SpecAttributeKind::RefineSpec => unreachable!("refine_spec on type"),
878                    SpecAttributeKind::Pure => unreachable!("pure on type"),
879                    SpecAttributeKind::Verified => unreachable!("verified on type"),
880                    SpecAttributeKind::Invariant => unreachable!("invariant on type"),
881                    SpecAttributeKind::Predicate => unreachable!("predicate on type"),
882                    SpecAttributeKind::Terminates => unreachable!("terminates on type"),
883                    SpecAttributeKind::Trusted | SpecAttributeKind::Model => {
884                        assert!(attr.tokens.is_empty(), "Unexpected shape of an attribute.");
885                        attr.tokens
886                    }
887                    SpecAttributeKind::PrintCounterexample => {
888                        // We need to drop the surrounding parenthesis to make the
889                        // tokens identical to the ones passed by the native procedural
890                        // macro call.
891                        let mut iter = attr.tokens.into_iter();
892                        let TokenTree::Group(group) = iter.next().unwrap() else {
893                            unreachable!()
894                        };
895                        group.stream()
896                    }
897                };
898                prusti_attributes.push((attr_kind, tokens));
899            } else {
900                regular_attributes.push(attr);
901            }
902        } else {
903            regular_attributes.push(attr);
904        }
905    }
906    item.attrs = regular_attributes;
907    prusti_attributes
908}
909
910/// Generate spec items and attributes for `item` from the Prusti attributes
911fn generate_spec_and_assertions_for_types(
912    mut prusti_attributes: Vec<(SpecAttributeKind, TokenStream)>,
913    item: &mut syn::DeriveInput,
914) -> GeneratedResult {
915    let mut generated_items = vec![];
916    let mut generated_attributes = vec![];
917
918    for (attr_kind, attr_tokens) in prusti_attributes.drain(..) {
919        let rewriting_result = match attr_kind {
920            SpecAttributeKind::Requires => unreachable!(),
921            SpecAttributeKind::Ensures => unreachable!(),
922            SpecAttributeKind::AfterExpiry => unreachable!(),
923            SpecAttributeKind::AssertOnExpiry => unreachable!(),
924            SpecAttributeKind::Pure => unreachable!(),
925            SpecAttributeKind::Verified => unreachable!(),
926            SpecAttributeKind::Predicate => unreachable!(),
927            SpecAttributeKind::Invariant => unreachable!(),
928            SpecAttributeKind::RefineSpec => unreachable!(),
929            SpecAttributeKind::Terminates => unreachable!(),
930            SpecAttributeKind::Trusted => generate_for_trusted_for_types(attr_tokens, item),
931            SpecAttributeKind::Model => generate_for_model(attr_tokens, item),
932            SpecAttributeKind::PrintCounterexample => {
933                generate_for_print_counterexample(attr_tokens, item)
934            }
935        };
936        let (new_items, new_attributes) = rewriting_result?;
937        generated_items.extend(new_items);
938        generated_attributes.extend(new_attributes);
939    }
940
941    Ok((generated_items, generated_attributes))
942}
943
944/// Generate spec items and attributes to typecheck and later retrieve "model" annotations.
945fn generate_for_model(attr: TokenStream, item: &mut syn::DeriveInput) -> GeneratedResult {
946    match syn::Item::from(item.clone()) {
947        syn::Item::Struct(item_struct) => {
948            match type_model::rewrite(item_struct) {
949                Ok(result) => {
950                    match result.first() {
951                        Some(syn::Item::Struct(new_item)) => {
952                            *item = syn::DeriveInput::from(new_item.clone()); //the internal model replaces the original struct
953                            Ok((vec![result[1].clone(), result[2].clone()], vec![]))
954                        }
955                        _ => unreachable!(),
956                    }
957                }
958                Err(err) => Err(err),
959            }
960        }
961        _ => Err(syn::Error::new(
962            attr.span(),
963            "Only structs can be attributed with a type model",
964        )),
965    }
966}
967
968/// Generate spec items and attributes to typecheck and later retrieve "print_counterexample" annotations.
969fn generate_for_print_counterexample(
970    attr: TokenStream,
971    item: &mut syn::DeriveInput,
972) -> GeneratedResult {
973    match syn::Item::from(item.clone()) {
974        syn::Item::Struct(item_struct) => {
975            match print_counterexample::rewrite_struct(attr, item_struct) {
976                Ok(result) => Ok((result, vec![])),
977                Err(err) => Err(err),
978            }
979        }
980        syn::Item::Enum(item_enum) => {
981            match print_counterexample::rewrite_enum(attr, item_enum) {
982                Ok(result) => {
983                    match result.first() {
984                        Some(syn::Item::Enum(new_item)) => {
985                            *item = syn::DeriveInput::from(new_item.clone()); //print_counterexample removes all attributes inside the enum
986                            Ok((vec![result[1].clone()], vec![]))
987                        }
988                        _ => unreachable!(),
989                    }
990                }
991                Err(err) => Err(err),
992            }
993        }
994        _ => Err(syn::Error::new(
995            attr.span(),
996            "Only structs and enums can be attributed with a custom counterexample print",
997        )),
998    }
999}
1000
1001pub fn type_model(attr: TokenStream, tokens: TokenStream) -> TokenStream {
1002    if syn::parse2::<syn::DeriveInput>(tokens.clone()).is_ok() {
1003        rewrite_prusti_attributes_for_types(SpecAttributeKind::Model, attr, tokens)
1004    } else {
1005        syn::Error::new(
1006            attr.span(),
1007            "Only structs can be attributed with a type model",
1008        )
1009        .to_compile_error()
1010    }
1011}
1012
1013pub fn print_counterexample(attr: TokenStream, tokens: TokenStream) -> TokenStream {
1014    if syn::parse2::<syn::DeriveInput>(tokens.clone()).is_ok() {
1015        rewrite_prusti_attributes_for_types(SpecAttributeKind::PrintCounterexample, attr, tokens)
1016    } else {
1017        syn::Error::new(
1018            attr.span(),
1019            "Only structs and enums can be attributed with print_counterexample",
1020        )
1021        .to_compile_error()
1022    }
1023}
1024pub fn ghost(tokens: TokenStream) -> TokenStream {
1025    let mut rewriter = rewriter::AstRewriter::new();
1026    let callsite_span = Span::call_site();
1027
1028    let spec_id = rewriter.generate_spec_id();
1029    let spec_id_str = spec_id.to_string();
1030
1031    let make_closure = |kind| {
1032        quote! {
1033            #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)]
1034            if false {
1035                #[prusti::spec_only]
1036                #[prusti::#kind]
1037                #[prusti::spec_id = #spec_id_str]
1038                || -> () {};
1039            }
1040        }
1041    };
1042
1043    struct Visitor {
1044        loops: Vec<(Option<syn::Ident>, Span)>,
1045        breaks: Vec<(Option<syn::Ident>, Span)>,
1046        returns: Option<Span>,
1047    }
1048
1049    impl<'ast> Visit<'ast> for Visitor {
1050        fn visit_expr_for_loop(&mut self, ex: &'ast syn::ExprForLoop) {
1051            let e = ex.clone();
1052            let lbl = e.label.map(|c| c.name.ident);
1053            let span = e.body.brace_token.span;
1054            self.loops.push((lbl, span));
1055            syn::visit::visit_expr_for_loop(self, ex);
1056        }
1057        fn visit_expr_while(&mut self, ex: &'ast syn::ExprWhile) {
1058            let e = ex.clone();
1059            let lbl = e.label.map(|c| c.name.ident);
1060            let span = e.body.brace_token.span;
1061            self.loops.push((lbl, span));
1062            syn::visit::visit_expr_while(self, ex);
1063        }
1064        fn visit_expr_loop(&mut self, ex: &'ast syn::ExprLoop) {
1065            let e = ex.clone();
1066            let lbl = e.label.map(|c| c.name.ident);
1067            let span = e.body.brace_token.span;
1068            self.loops.push((lbl, span));
1069            syn::visit::visit_expr_loop(self, ex);
1070        }
1071        fn visit_expr_continue(&mut self, ex: &'ast syn::ExprContinue) {
1072            let e = ex.clone();
1073            let lbl = e.label.map(|c| c.ident);
1074            self.breaks.push((lbl, ex.span()));
1075            syn::visit::visit_expr_continue(self, ex);
1076        }
1077        fn visit_expr_break(&mut self, ex: &'ast syn::ExprBreak) {
1078            let e = ex.clone();
1079            let lbl = e.label.map(|c| c.ident);
1080            self.breaks.push((lbl, ex.span()));
1081            syn::visit::visit_expr_break(self, ex);
1082        }
1083        fn visit_expr_return(&mut self, e: &'ast syn::ExprReturn) {
1084            let e = e.clone();
1085            self.returns = Some(e.span());
1086        }
1087    }
1088
1089    let mut visitor = Visitor {
1090        loops: vec![],
1091        breaks: vec![],
1092        returns: None,
1093    };
1094
1095    let tokens = quote! {
1096        {#tokens}
1097    };
1098
1099    let input = syn::parse::<syn::Block>(tokens.clone().into()).unwrap();
1100
1101    visitor.visit_block(&input);
1102
1103    let mut exit_errors = visitor.returns.into_iter().collect::<Vec<_>>();
1104
1105    'breaks: for (break_label, break_span) in visitor.breaks.iter() {
1106        for (loop_label, loop_span) in visitor.loops.iter() {
1107            let loop_span = loop_span.unwrap();
1108            let label_match = break_label == loop_label || break_label.is_none();
1109            let break_inside = loop_span.join(break_span.unwrap()).unwrap().eq(&loop_span);
1110            if label_match && break_inside {
1111                continue 'breaks;
1112            }
1113        }
1114        exit_errors.push(*break_span);
1115    }
1116
1117    let begin = make_closure(quote! {ghost_begin});
1118    let end = make_closure(quote! {ghost_end});
1119
1120    if exit_errors.is_empty() {
1121        quote_spanned! {callsite_span=>
1122            {
1123                #begin
1124                #[prusti::specs_version = #SPECS_VERSION]
1125                let ghost_result = Ghost::new(#tokens);
1126                #end
1127                ghost_result
1128            }
1129        }
1130    } else {
1131        let mut syn_errors = quote! {};
1132        for error in exit_errors {
1133            let error =
1134                syn::Error::new(error, "Can't leave the ghost block early").to_compile_error();
1135            syn_errors = quote! {
1136                #syn_errors
1137                #error
1138            }
1139        }
1140        syn_errors
1141    }
1142}